Complete Yocto mirror with license table for TQMa6UL (2038-compliance)

- 264 license table entries with exact download URLs (224/264 resolved)
- Complete sources/ directory with all BitBake recipes
- Build configuration: tqma6ul-multi-mba6ulx, spaetzle (musl)
- Full traceability for Softwarefreigabeantrag
- GCC 13.4.0, Linux 6.6.102, U-Boot 2023.04, musl 1.2.4
- License distribution: GPL-2.0 (24), MIT (23), GPL-2.0+ (18), BSD-3 (16)
This commit is contained in:
Siggi (OpenClaw Agent)
2026-03-01 20:58:18 +00:00
commit 16accb6b24
15086 changed files with 1292356 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"curly" : false,
"predef" : [ "$","libtoaster", "prettyPrint" ],
"eqnull": true,
"plusplus" : false,
"browser" : true,
"jquery" : true,
"devel" : true,
"unused" : true,
"maxerr" : 60
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,322 @@
"use strict";
function customRecipePageInit(ctx) {
var urlParams = libtoaster.parseUrlParams();
var customiseTable = $("#selectpackagestable");
var addPkgDepsModalBtn = $("#add-package-deps-modal-btn");
var rmdPkgReverseDepsModalBtn = $("#rm-package-reverse-deps-modal-btn");
if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new'){
$("#image-created-notification").show();
}
customiseTable.on('table-done', function(e, total){
/* Table is done so now setup the click handler for the package buttons */
$(".add-rm-package-btn").click(function(e){
e.preventDefault();
var targetPkg = $(this).data();
checkPackageDeps(targetPkg, function(pkgData){
if (targetPkg.directive === 'add'){
/* If we're adding a package we may need to show the modal to advise
* on dependencies for this package.
*/
if (pkgData.unsatisfied_dependencies.length === 0){
addRemovePackage(targetPkg);
} else {
showPackageDepsModal(targetPkg, pkgData);
}
} else if (targetPkg.directive === 'remove') {
if (pkgData.reverse_dependencies.length === 0){
addRemovePackage(targetPkg);
} else {
showPackageReverseDepsModal(targetPkg, pkgData);
}
}
});
});
});
function checkPackageDeps(targetPkg, doneCb){
$.ajax({
type: 'GET',
url: targetPkg.packageUrl,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function(data){
if (data.error !== 'ok'){
console.warn(data.error);
return;
}
doneCb(data);
}
});
}
function showPackageDepsModal(targetPkg, pkgData){
var modal = $("#package-deps-modal");
var depsList = modal.find("#package-add-dep-list");
var deps = pkgData.unsatisfied_dependencies;
modal.find(".package-to-add-name").text(targetPkg.name);
depsList.text("");
for (var i in deps){
var li = $('<li></li>').text(deps[i].name);
li.append($('<span></span>').text(" ("+
deps[i].size_formatted+")"));
depsList.append(li);
}
modal.find("#package-deps-total-size").text(
pkgData.unsatisfied_dependencies_size_formatted);
targetPkg.depsAdded = deps;
addPkgDepsModalBtn.data(targetPkg);
modal.modal('show');
}
addPkgDepsModalBtn.click(function(e){
e.preventDefault();
addRemovePackage($(this).data(), null);
});
function showPackageReverseDepsModal(targetPkg, pkgData){
var modal = $("#package-reverse-deps-modal");
var depsList = modal.find("#package-reverse-dep-list");
var deps = pkgData.reverse_dependencies;
var depsCount = deps.length;
var vDepends = "depends";
var vPackage = "package";
var vThis = "this";
if (depsCount > 1) {
vDepends = "depend";
vPackage = "packages";
vThis = "these";
}
modal.find(".package-to-rm-name").text(targetPkg.name);
modal.find(".reverse-deps-count").text(depsCount);
modal.find(".reverse-deps-count-plus1").text((depsCount+1) + " packages");
modal.find(".reverse-deps-depends").text(vDepends);
modal.find(".reverse-deps-package").text(vPackage);
modal.find(".reverse-deps-this").text(vThis);
depsList.text("");
for (var i in deps){
var li = $('<li></li>').text(deps[i].name);
li.append($('<span></span>').text(" ("+
deps[i].size_formatted+")"));
depsList.append(li);
}
modal.find("#package-reverse-deps-total-size").text(
pkgData.reverse_dependencies_size_formatted);
targetPkg.depsRemoved = deps;
rmdPkgReverseDepsModalBtn.data(targetPkg);
modal.modal('show');
}
rmdPkgReverseDepsModalBtn.click(function(e){
e.preventDefault();
addRemovePackage($(this).data(), null);
});
function addRemovePackage(targetPkg, tableParams){
var method;
var msg = "You have ";
var btnCell = $("#package-btn-cell-" + targetPkg.id);
var inlineNotify = btnCell.children(".inline-notification");
var i;
var dep;
var depBtnCell;
if (targetPkg.directive === 'add') {
method = 'PUT';
/* If the package had dependencies also notify that they were added */
if (targetPkg.hasOwnProperty('depsAdded') &&
targetPkg.depsAdded.length > 0) {
msg += "added ";
msg += "<strong>" + (targetPkg.depsAdded.length + 1) + "</strong>";
msg += " packages to " + ctx.recipe.name + ": ";
msg += "<strong>" + targetPkg.name + "</strong> and its dependencies";
for (i in targetPkg.depsAdded){
dep = targetPkg.depsAdded[i];
msg += " <strong>" + dep.name + "</strong>";
/* Add any cells currently in view to the list of cells which get
* an list-inline notification inside them and which change add/rm state
*/
depBtnCell = $("#package-btn-cell-" + dep.pk);
btnCell = btnCell.add(depBtnCell);
inlineNotify = inlineNotify.add(
depBtnCell.children(".inline-notification"));
}
inlineNotify.text(
(targetPkg.depsAdded.length + 1) + " packages added");
} else {
msg += "added <strong>1</strong>";
msg += " package to " + ctx.recipe.name + ": ";
msg += "<strong>" + targetPkg.name + "</strong>";
inlineNotify.text("1 package added");
}
} else if (targetPkg.directive === 'remove') {
method = 'DELETE';
var numPackageString = "1 package ";
var revDepList = "";
if (targetPkg.hasOwnProperty('depsRemoved') &&
targetPkg.depsRemoved.length > 0) {
var depsRemovedLength = targetPkg.depsRemoved.length;
var ending = "y: ";
var maxRevDepsDisplayed = 5;
var d = 0;
if (depsRemovedLength > 1) {
ending = "ies: ";
}
numPackageString = (depsRemovedLength + 1) + " packages";
revDepList = " and its " + depsRemovedLength + " reverse dependenc" + ending;
for (i in targetPkg.depsRemoved){
/* include up to maxRevDepsDisplayed rev deps on the page notification */
var notShownCount = depsRemovedLength - maxRevDepsDisplayed;
dep = targetPkg.depsRemoved[i];
if (d < maxRevDepsDisplayed) {
if (d > 0) {
revDepList += ", ";
}
revDepList += dep.name;
d++;
if ((d === maxRevDepsDisplayed) && (notShownCount > 0)) {
revDepList += " and " + notShownCount + " more";
}
}
/* Add any cells currently in view to the list of cells which get
* an list-inline notification inside them and which change add/rm state
*/
depBtnCell = $("#package-btn-cell-" + dep.pk);
btnCell = btnCell.add(depBtnCell);
inlineNotify = inlineNotify.add(
depBtnCell.children(".inline-notification"));
}
}
msg+= "removed " + numPackageString + " from " + ctx.recipe.name + ":";
msg += " <strong>" + targetPkg.name + "</strong>";
msg += revDepList;
inlineNotify.text(numPackageString + " removed");
} else {
throw("Unknown package directive: should be add or remove");
}
$.ajax({
type: method,
url: targetPkg.packageUrl,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function(data){
if (data.error !== 'ok'){
console.warn(data.error);
return;
}
libtoaster.showChangeNotification(msg);
/* do the in-cell/inline notification to swap buttoms from add to
* remove
*/
btnCell.children("button").fadeOut().promise().done(function(){
inlineNotify.fadeIn().delay(500).fadeOut(function(){
if (targetPkg.directive === 'add')
btnCell.children("button[data-directive=remove]").fadeIn();
else
btnCell.children("button[data-directive=add]").fadeIn();
});
});
/* Update the total num packages */
$.ajax({
type: "GET",
url: ctx.recipe.xhrPackageListUrl,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function(data){
console.log(data);
$("#total-num-packages").text(data.total);
$("#total-size-packages").text(data.total_size_formatted);
}
});
}
});
}
$("#no-results-show-all-packages").click(function(){
$(".no-results-search-input").val("");
});
$("#no-results-remove-search-btn").click(function(){
$(".no-results-search-input").val("");
$(this).hide();
});
/* Trigger a build of your custom image */
$(".build-custom-image").click(function(){
libtoaster.startABuild(null, ctx.recipe.name,
function(){
window.location.replace(libtoaster.ctx.projectBuildsUrl);
});
});
$("#delete-custom-recipe-confirmed").click(function(e){
e.preventDefault();
libtoaster.disableAjaxLoadingTimer();
$(this).find('[data-role="submit-state"]').hide();
$(this).find('[data-role="loading-state"]').show();
$(this).attr("disabled", "disabled");
$.ajax({
type: 'DELETE',
url: ctx.recipe.xhrCustomRecipeUrl,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error !== "ok") {
console.warn(data.error);
} else {
var msg = $('<span>You have deleted <strong>1</strong> custom image: <strong id="deleted-custom-image-name"></strong></span>');
msg.find("#deleted-custom-image-name").text(ctx.recipe.name);
libtoaster.setNotification("custom-image-recipe-deleted",
msg.html());
window.location.replace(data.gotoUrl);
}
},
error: function (data) {
console.warn(data);
}
});
});
/* Stop the download link from working if it is in disabled state
* http://getbootstrap.com/css/#forms-disabled-fieldsets
*/
$("a[disabled=disabled]").click(function(e){
e.preventDefault();
});
}

View File

@@ -0,0 +1,94 @@
"use strict"
// The disable removes the 'datepicker' attribute and
// settings, so you have to re-initialize it each time
// the date range is selected and enabled
// DOM is used instead of jQuery to find the elements
// in all contexts
function date_enable (key, action) {
var elemFrom=document.getElementById("date_from_"+key);
var elemTo=document.getElementById("date_to_"+key);
if ('enable' == action) {
elemFrom.removeAttribute("disabled");
elemTo.removeAttribute("disabled");
$(elemFrom).datepicker();
$(elemTo).datepicker();
$(elemFrom).datepicker( "option", "dateFormat", "dd/mm/yy" );
$(elemTo).datepicker( "option", "dateFormat", "dd/mm/yy" );
$(elemFrom).datepicker( "setDate", elemFrom.getAttribute( "data-setDate") );
$(elemTo).datepicker( "setDate", elemTo.getAttribute( "data-setDate") );
$(elemFrom).datepicker( "option", "minDate", elemFrom.getAttribute( "data-minDate"));
$(elemTo).datepicker( "option", "minDate", elemTo.getAttribute( "data-minDate"));
$(elemFrom).datepicker( "option", "maxDate", elemFrom.getAttribute( "data-maxDate"));
$(elemTo).datepicker( "option", "maxDate", elemTo.getAttribute( "data-maxDate"));
} else {
elemFrom.setAttribute("disabled","disabled");
elemTo.setAttribute("disabled","disabled");
}
}
// Initialize the date picker elements with their default state variables, and
// register the radio button and form actions
function date_init (key, from_date, to_date, min_date, max_date, initial_enable) {
var elemFrom=document.getElementById("date_from_"+key);
var elemTo=document.getElementById("date_to_"+key);
// Were there any daterange filters instantiated? (e.g. no builds found)
if (null == elemFrom) {
return;
}
// init the datepicker context data
elemFrom.setAttribute( "data-setDate", from_date );
elemTo.setAttribute( "data-setDate", to_date );
elemFrom.setAttribute( "data-minDate", min_date);
elemTo.setAttribute( "data-minDate", min_date);
elemFrom.setAttribute( "data-maxDate", max_date);
elemTo.setAttribute( "data-maxDate", max_date);
// does the date set start enabled?
if (key == initial_enable) {
date_enable (key, "enable");
} else {
date_enable (key, "disable");
}
// catch the radio button selects for enable/disable
$('input:radio[name="filter"]').change(function(){
if ($(this).val() == 'daterange') {
key=$(this).attr("data-key");
date_enable (key, 'enable');
} else {
key=$(this).attr("data-key");
date_enable (key, 'disable');
}
});
// catch any new 'from' date as minDate for 'to' date
$("#date_from_"+key).change(function(){
from_date = $("#date_from_"+key).val();
$("#date_to_"+key).datepicker( "option", "minDate", from_date );
});
// catch the submit (just once)
$("form").unbind('submit');
$("form").submit(function(e) {
// format a composite daterange filter value so that it can be parsed and post-processed in the view
if (key !== undefined) {
if ($("#date_from_"+key).length) {
var filter=key+"__gte!"+key+"__lt:"+$("#date_from_"+key).val()+"!"+$("#date_to_"+key).val()+"_daterange";
$("#last_date_from_"+key).val($("#date_from_"+key).val());
$("#last_date_to_"+key).val($("#date_to_"+key).val());
$("#filter_value_"+key).val(filter);
}
}
return true;
});
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,455 @@
"use strict"
function importLayerPageInit (ctx) {
var layerDepBtn = $("#add-layer-dependency-btn");
var importAndAddBtn = $("#import-and-add-btn");
var layerNameInput = $("#import-layer-name");
var vcsURLInput = $("#layer-git-repo-url");
var gitRefInput = $("#layer-git-ref");
var layerDepInput = $("#layer-dependency");
var layerNameCtrl = $("#layer-name-ctrl");
var duplicatedLayerName = $("#duplicated-layer-name-hint");
var localDirPath = $("#local-dir-path");
var layerDeps = {};
var layerDepsDeps = {};
var currentLayerDepSelection;
var validLayerName = /^(\w|-)+$/;
/* Catch 'disable' race condition between type-ahead started and "input change" */
var typeAheadStarted = 0;
libtoaster.makeTypeahead(layerDepInput,
libtoaster.ctx.layersTypeAheadUrl,
{ include_added: "true" }, function(item){
currentLayerDepSelection = item;
layerDepBtn.removeAttr("disabled");
typeAheadStarted = 1;
});
layerDepInput.on("typeahead:select", function(event, data){
currentLayerDepSelection = data;
});
// Disable local dir repo when page is loaded.
$('#local-dir').hide();
// disable the "Add layer" button when the layer input typeahead is empty
// or not in the typeahead choices
layerDepInput.on("input change", function(){
if (0 == typeAheadStarted) {
layerDepBtn.attr("disabled","disabled");
}
typeAheadStarted = 0;
});
/* We automatically add "openembedded-core" layer for convenience as a
* dependency as pretty much all layers depend on this one
*/
$.getJSON(libtoaster.ctx.layersTypeAheadUrl,
{ include_added: "true" , search: "openembedded-core" },
function(layer) {
if (layer.results.length > 0) {
currentLayerDepSelection = layer.results[0];
layerDepBtn.click();
}
});
layerDepBtn.click(function(){
typeAheadStarted = 0;
if (currentLayerDepSelection == undefined)
return;
layerDeps[currentLayerDepSelection.id] = currentLayerDepSelection;
/* Make a list item for the new layer dependency */
var newLayerDep = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Remove\"></span></li>");
newLayerDep.data('layer-id', currentLayerDepSelection.id);
newLayerDep.children("span").tooltip();
var link = newLayerDep.children("a");
link.attr("href", currentLayerDepSelection.layerdetailurl);
link.text(currentLayerDepSelection.name);
link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"});
var trashItem = newLayerDep.children("span");
trashItem.click(function () {
var toRemove = $(this).parent().data('layer-id');
delete layerDeps[toRemove];
$(this).parent().fadeOut(function (){
$(this).remove();
});
});
$("#layer-deps-list").append(newLayerDep);
libtoaster.getLayerDepsForProject(currentLayerDepSelection.xhrLayerUrl,
function (data){
/* These are the dependencies of the layer added as a dependency */
if (data.list.length > 0) {
currentLayerDepSelection.url = currentLayerDepSelection.layerdetailurl;
layerDeps[currentLayerDepSelection.id].deps = data.list;
}
/* Clear the current selection */
layerDepInput.val("");
currentLayerDepSelection = undefined;
layerDepBtn.attr("disabled","disabled");
}, null);
});
importAndAddBtn.click(function(e){
e.preventDefault();
/* This is a list of the names from layerDeps for the layer deps
* modal dialog body
*/
var depNames = [];
/* arrray of all layer dep ids includes parent and child deps */
var allDeps = [];
/* temporary object to use to do a reduce on the dependencies for each
* layer dependency added
*/
var depDeps = {};
/* the layers that have dependencies have an extra property "deps"
* look in this for each layer and reduce this to a unquie object
* of deps.
*/
for (var key in layerDeps){
if (layerDeps[key].hasOwnProperty('deps')){
for (var dep in layerDeps[key].deps){
var layer = layerDeps[key].deps[dep];
depDeps[layer.id] = layer;
}
}
depNames.push(layerDeps[key].name);
allDeps.push(layerDeps[key].id);
}
/* we actually want it as an array so convert it now */
var depDepsArray = [];
for (var key in depDeps)
depDepsArray.push (depDeps[key]);
if (depDepsArray.length > 0) {
var layer = { name: layerNameInput.val(), url: "#", id: -1 };
var title = "Layer";
var body = "<strong>"+layer.name+"</strong>'s dependencies ("+
depNames.join(", ")+"</span>) require some layers that are not added to your project. Select the ones you want to add:</p>";
showLayerDepsModal(layer,
depDepsArray,
title, body, false, function(layerObsList){
/* Add the accepted layer dependencies' ids to the allDeps array */
for (var key in layerObsList){
allDeps.push(layerObsList[key].id);
}
import_and_add ();
});
} else {
import_and_add ();
}
function import_and_add () {
/* convert to a csv of all the deps to be added */
var layerDepsCsv = allDeps.join(",");
var layerData = {
name: layerNameInput.val(),
vcs_url: vcsURLInput.val(),
git_ref: gitRefInput.val(),
dir_path: $("#layer-subdir").val(),
project_id: libtoaster.ctx.projectId,
layer_deps: layerDepsCsv,
local_source_dir: $('#local-dir-path').val(),
add_to_project: true,
};
if ($('input[name=repo]:checked').val() == "git") {
layerData.local_source_dir = "";
} else {
layerData.vcs_url = "";
layerData.git_ref = "";
}
$.ajax({
type: "PUT",
url: ctx.xhrLayerUrl,
data: JSON.stringify(layerData),
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error != "ok") {
console.log(data.error);
/* let the user know why nothing happened */
alert(data.error)
} else {
createImportedNotification(data);
window.location.replace(libtoaster.ctx.projectPageUrl);
}
},
error: function (data) {
console.log("Call failed");
console.log(data);
}
});
}
});
/* Layer imported notification */
function createImportedNotification(imported){
var message = "Layer imported";
if (imported.deps_added.length === 0) {
message = "You have imported <strong><a class=\"alert-link\" href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a></strong> and added it to your project.";
} else {
var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, ";
imported.deps_added.map (function(item, index){
links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>';
/*If we're at the last element we don't want the trailing comma */
if (imported.deps_added[index+1] !== undefined)
links += ', ';
});
/* Length + 1 here to do deps + the imported layer */
message = 'You have imported <strong><a href="'+imported.imported_layer.layerdetailurl+'">'+imported.imported_layer.name+'</a></strong> and added <strong>'+(imported.deps_added.length+1)+'</strong> layers to your project: <strong>'+links+'</strong>';
}
libtoaster.setNotification("layer-imported", message);
}
function enable_import_btn(enabled) {
var importAndAddHint = $("#import-and-add-hint");
if (enabled) {
importAndAddBtn.removeAttr("disabled");
importAndAddHint.hide();
return;
}
importAndAddBtn.attr("disabled", "disabled");
importAndAddHint.show();
}
function check_form() {
var valid = false;
var inputs = $("input:required");
var inputStr = inputs.val().split("");
for (var i=0; i<inputs.val().length; i++){
if (!(valid = inputStr[i])){
enable_import_btn(false);
break;
}
}
if (valid) {
if ($("#local-dir-radio").prop("checked") &&
localDirPath.val().length > 0) {
enable_import_btn(true);
}
if ($("#git-repo-radio").prop("checked")) {
if (gitRefInput.val().length > 0 &&
gitRefInput.val() == 'HEAD') {
$('#invalid-layer-revision-hint').show();
$('#layer-revision-ctrl').addClass('has-error');
enable_import_btn(false);
} else if (vcsURLInput.val().length > 0 &&
gitRefInput.val().length > 0) {
$('#invalid-layer-revision-hint').hide();
$('#layer-revision-ctrl').removeClass('has-error');
enable_import_btn(true);
}
}
}
if (inputs.val().length == 0)
enable_import_btn(false);
}
function layerExistsError(layer){
var dupLayerInfo = $("#duplicate-layer-info");
if (layer.local_source_dir) {
$("#git-layer-dup").hide();
$("#local-layer-dup").fadeIn();
dupLayerInfo.find(".dup-layer-name").text(layer.name);
dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl);
dupLayerInfo.find("#dup-local-source-dir-name").text(layer.local_source_dir);
} else {
$("#git-layer-dup").fadeIn();
$("#local-layer-dup").hide();
dupLayerInfo.find(".dup-layer-name").text(layer.name);
dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl);
dupLayerInfo.find("#dup-layer-vcs-url").text(layer.vcs_url);
dupLayerInfo.find("#dup-layer-revision").text(layer.vcs_reference);
}
$(".fields-apart-from-layer-name").fadeOut(function(){
dupLayerInfo.fadeIn();
});
}
layerNameInput.on('blur', function() {
if (!$(this).val()){
return;
}
var name = $(this).val();
/* Check if the layer name exists */
$.getJSON(libtoaster.ctx.layersTypeAheadUrl,
{ include_added: "true" , search: name, format: "json" },
function(layer) {
if (layer.results.length > 0) {
for (var i in layer.results){
if (layer.results[i].name == name) {
layerExistsError(layer.results[i]);
}
}
}
});
});
vcsURLInput.on('input', function() {
check_form();
});
gitRefInput.on('input', function() {
check_form();
});
layerNameInput.on('input', function() {
if ($(this).val() && !validLayerName.test($(this).val())){
layerNameCtrl.addClass("has-error")
$("#invalid-layer-name-hint").show();
enable_import_btn(false);
return;
}
if ($("#duplicate-layer-info").css("display") != "None"){
$("#duplicate-layer-info").fadeOut(function(){
$(".fields-apart-from-layer-name").show();
radioDisplay();
});
}
radioDisplay();
/* Don't remove the error class if we're displaying the error for another
* reason.
*/
if (!duplicatedLayerName.is(":visible"))
layerNameCtrl.removeClass("has-error")
$("#invalid-layer-name-hint").hide();
check_form();
});
/* Setup 'blank' typeahead */
libtoaster.makeTypeahead(gitRefInput,
ctx.xhrGitRevTypeAheadUrl,
{ git_url: null }, function(){});
vcsURLInput.focusout(function (){
if (!$(this).val())
return;
/* If we a layer name specified don't overwrite it or if there isn't a
* url typed in yet return
*/
if (!layerNameInput.val() && $(this).val().search("/")){
var urlPts = $(this).val().split("/");
/* Add a suggestion of the layer name */
var suggestion = urlPts[urlPts.length-1].replace(".git","");
layerNameInput.val(suggestion);
}
/* Now actually setup the typeahead properly with the git url entered */
gitRefInput._typeahead('destroy');
libtoaster.makeTypeahead(gitRefInput,
ctx.xhrGitRevTypeAheadUrl,
{ git_url: $(this).val() },
function(selected){
gitRefInput._typeahead("close");
});
});
function radioDisplay() {
if ($('input[name=repo]:checked').val() == "local") {
$('#git-repo').hide();
$('#import-git-layer-and-add-hint').hide();
$('#local-dir').fadeIn();
$('#import-local-dir-and-add-hint').fadeIn();
} else {
$('#local-dir').hide();
$('#import-local-dir-and-add-hint').hide();
$('#git-repo').fadeIn();
$('#import-git-layer-and-add-hint').fadeIn();
}
}
$('input:radio[name="repo"]').change(function() {
radioDisplay();
if ($("#local-dir-radio").prop("checked")) {
if (localDirPath.val().length > 0) {
enable_import_btn(true);
} else {
enable_import_btn(false);
}
}
if ($("#git-repo-radio").prop("checked")) {
if (vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) {
enable_import_btn(true);
} else {
enable_import_btn(false);
}
}
});
localDirPath.on('input', function(){
if ($(this).val().trim().length == 0) {
$('#import-and-add-btn').attr("disabled","disabled");
$('#local-dir').addClass('has-error');
$('#hintError-dir-abs-path').show();
$('#hintError-dir-path-starts-with-slash').show();
} else {
var input = $(this);
var reBeginWithSlash = /^\//;
var reCheckVariable = /^\$/;
var re = /([ <>\\|":%\?\*]+)/;
var invalidDir = re.test(input.val());
var invalidSlash = reBeginWithSlash.test(input.val());
var invalidVar = reCheckVariable.test(input.val());
if (!invalidSlash && !invalidVar) {
$('#local-dir').addClass('has-error');
$('#import-and-add-btn').attr("disabled","disabled");
$('#hintError-dir-abs-path').show();
$('#hintError-dir-path-starts-with-slash').show();
} else if (invalidDir) {
$('#local-dir').addClass('has-error');
$('#import-and-add-btn').attr("disabled","disabled");
$('#hintError-dir-path').show();
} else {
$('#local-dir').removeClass('has-error');
if (layerNameInput.val().length > 0) {
$('#import-and-add-btn').removeAttr("disabled");
}
$('#hintError-dir-abs-path').hide();
$('#hintError-dir-path-starts-with-slash').hide();
$('#hintError-dir-path').hide();
}
}
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,117 @@
/*!
* jQuery Cookie Plugin v1.4.0
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define(['jquery'], factory);
} else {
// Browser globals.
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
} catch(e) {
return;
}
try {
// If we can't parse the cookie, ignore it, it's unusable.
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== undefined) {
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return true;
}
return false;
};
}));

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,620 @@
/*
* jQuery treetable Plugin 3.1.0
* http://ludo.cubicphuse.nl/jquery-treetable
*
* Copyright 2013, Ludo van den Boom
* Dual licensed under the MIT or GPL Version 2 licenses.
*/
(function() {
var $, Node, Tree, methods;
$ = jQuery;
Node = (function() {
function Node(row, tree, settings) {
var parentId;
this.row = row;
this.tree = tree;
this.settings = settings;
// TODO Ensure id/parentId is always a string (not int)
this.id = this.row.data(this.settings.nodeIdAttr);
// TODO Move this to a setParentId function?
parentId = this.row.data(this.settings.parentIdAttr);
if (parentId != null && parentId !== "") {
this.parentId = parentId;
}
this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]);
this.expander = $(this.settings.expanderTemplate);
this.indenter = $(this.settings.indenterTemplate);
this.children = [];
this.initialized = false;
this.treeCell.prepend(this.indenter);
}
Node.prototype.addChild = function(child) {
return this.children.push(child);
};
Node.prototype.ancestors = function() {
var ancestors, node;
node = this;
ancestors = [];
while (node = node.parentNode()) {
ancestors.push(node);
}
return ancestors;
};
Node.prototype.collapse = function() {
if (this.collapsed()) {
return this;
}
this.row.removeClass("expanded").addClass("collapsed");
this._hideChildren();
this.expander.attr("title", this.settings.stringExpand);
if (this.initialized && this.settings.onNodeCollapse != null) {
this.settings.onNodeCollapse.apply(this);
}
return this;
};
Node.prototype.collapsed = function() {
return this.row.hasClass("collapsed");
};
// TODO destroy: remove event handlers, expander, indenter, etc.
Node.prototype.expand = function() {
if (this.expanded()) {
return this;
}
this.row.removeClass("collapsed").addClass("expanded");
if (this.initialized && this.settings.onNodeExpand != null) {
this.settings.onNodeExpand.apply(this);
}
if ($(this.row).is(":visible")) {
this._showChildren();
}
this.expander.attr("title", this.settings.stringCollapse);
return this;
};
Node.prototype.expanded = function() {
return this.row.hasClass("expanded");
};
Node.prototype.hide = function() {
this._hideChildren();
this.row.hide();
return this;
};
Node.prototype.isBranchNode = function() {
if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) {
return true;
} else {
return false;
}
};
Node.prototype.updateBranchLeafClass = function(){
this.row.removeClass('branch');
this.row.removeClass('leaf');
this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf');
};
Node.prototype.level = function() {
return this.ancestors().length;
};
Node.prototype.parentNode = function() {
if (this.parentId != null) {
return this.tree[this.parentId];
} else {
return null;
}
};
Node.prototype.removeChild = function(child) {
var i = $.inArray(child, this.children);
return this.children.splice(i, 1)
};
Node.prototype.render = function() {
var handler,
settings = this.settings,
target;
if (settings.expandable === true && this.isBranchNode()) {
handler = function(e) {
$(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle();
return e.preventDefault();
};
this.indenter.html(this.expander);
target = settings.clickableNodeNames === true ? this.treeCell : this.expander;
target.off("click.treetable").on("click.treetable", handler);
target.off("keydown.treetable").on("keydown.treetable", function(e) {
if (e.keyCode == 13) {
handler.apply(this, [e]);
}
});
}
this.indenter[0].style.paddingLeft = "" + (this.level() * settings.indent) + "px";
return this;
};
Node.prototype.reveal = function() {
if (this.parentId != null) {
this.parentNode().reveal();
}
return this.expand();
};
Node.prototype.setParent = function(node) {
if (this.parentId != null) {
this.tree[this.parentId].removeChild(this);
}
this.parentId = node.id;
this.row.data(this.settings.parentIdAttr, node.id);
return node.addChild(this);
};
Node.prototype.show = function() {
if (!this.initialized) {
this._initialize();
}
this.row.show();
if (this.expanded()) {
this._showChildren();
}
return this;
};
Node.prototype.toggle = function() {
if (this.expanded()) {
this.collapse();
} else {
this.expand();
}
return this;
};
Node.prototype._hideChildren = function() {
var child, _i, _len, _ref, _results;
_ref = this.children;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
_results.push(child.hide());
}
return _results;
};
Node.prototype._initialize = function() {
var settings = this.settings;
this.render();
if (settings.expandable === true && settings.initialState === "collapsed") {
this.collapse();
} else {
this.expand();
}
if (settings.onNodeInitialized != null) {
settings.onNodeInitialized.apply(this);
}
return this.initialized = true;
};
Node.prototype._showChildren = function() {
var child, _i, _len, _ref, _results;
_ref = this.children;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
_results.push(child.show());
}
return _results;
};
return Node;
})();
Tree = (function() {
function Tree(table, settings) {
this.table = table;
this.settings = settings;
this.tree = {};
// Cache the nodes and roots in simple arrays for quick access/iteration
this.nodes = [];
this.roots = [];
}
Tree.prototype.collapseAll = function() {
var node, _i, _len, _ref, _results;
_ref = this.nodes;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
_results.push(node.collapse());
}
return _results;
};
Tree.prototype.expandAll = function() {
var node, _i, _len, _ref, _results;
_ref = this.nodes;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
_results.push(node.expand());
}
return _results;
};
Tree.prototype.findLastNode = function (node) {
if (node.children.length > 0) {
return this.findLastNode(node.children[node.children.length - 1]);
} else {
return node;
}
};
Tree.prototype.loadRows = function(rows) {
var node, row, i;
if (rows != null) {
for (i = 0; i < rows.length; i++) {
row = $(rows[i]);
if (row.data(this.settings.nodeIdAttr) != null) {
node = new Node(row, this.tree, this.settings);
this.nodes.push(node);
this.tree[node.id] = node;
if (node.parentId != null) {
this.tree[node.parentId].addChild(node);
} else {
this.roots.push(node);
}
}
}
}
for (i = 0; i < this.nodes.length; i++) {
node = this.nodes[i].updateBranchLeafClass();
}
return this;
};
Tree.prototype.move = function(node, destination) {
// Conditions:
// 1: +node+ should not be inserted as a child of +node+ itself.
// 2: +destination+ should not be the same as +node+'s current parent (this
// prevents +node+ from being moved to the same location where it already
// is).
// 3: +node+ should not be inserted in a location in a branch if this would
// result in +node+ being an ancestor of itself.
var nodeParent = node.parentNode();
if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) {
node.setParent(destination);
this._moveRows(node, destination);
// Re-render parentNode if this is its first child node, and therefore
// doesn't have the expander yet.
if (node.parentNode().children.length === 1) {
node.parentNode().render();
}
}
if(nodeParent){
nodeParent.updateBranchLeafClass();
}
if(node.parentNode()){
node.parentNode().updateBranchLeafClass();
}
node.updateBranchLeafClass();
return this;
};
Tree.prototype.removeNode = function(node) {
// Recursively remove all descendants of +node+
this.unloadBranch(node);
// Remove node from DOM (<tr>)
node.row.remove();
// Clean up Tree object (so Node objects are GC-ed)
delete this.tree[node.id];
this.nodes.splice($.inArray(node, this.nodes), 1);
}
Tree.prototype.render = function() {
var root, _i, _len, _ref;
_ref = this.roots;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
root = _ref[_i];
// Naming is confusing (show/render). I do not call render on node from
// here.
root.show();
}
return this;
};
Tree.prototype.sortBranch = function(node, sortFun) {
// First sort internal array of children
node.children.sort(sortFun);
// Next render rows in correct order on page
this._sortChildRows(node);
return this;
};
Tree.prototype.unloadBranch = function(node) {
var children, i;
for (i = 0; i < node.children.length; i++) {
this.removeNode(node.children[i]);
}
// Reset node's collection of children
node.children = [];
node.updateBranchLeafClass();
return this;
};
Tree.prototype._moveRows = function(node, destination) {
var children = node.children, i;
node.row.insertAfter(destination.row);
node.render();
// Loop backwards through children to have them end up on UI in correct
// order (see #112)
for (i = children.length - 1; i >= 0; i--) {
this._moveRows(children[i], node);
}
};
// Special _moveRows case, move children to itself to force sorting
Tree.prototype._sortChildRows = function(parentNode) {
return this._moveRows(parentNode, parentNode);
};
return Tree;
})();
// jQuery Plugin
methods = {
init: function(options, force) {
var settings;
settings = $.extend({
branchAttr: "ttBranch",
clickableNodeNames: false,
column: 0,
columnElType: "td", // i.e. 'td', 'th' or 'td,th'
expandable: false,
expanderTemplate: "<a href='#'>&nbsp;</a>",
indent: 10,
indenterTemplate: "<span class='indenter'></span>",
initialState: "collapsed",
nodeIdAttr: "ttId", // maps to data-tt-id
parentIdAttr: "ttParentId", // maps to data-tt-parent-id
stringExpand: "Expand",
stringCollapse: "Collapse",
// Events
onInitialized: null,
onNodeCollapse: null,
onNodeExpand: null,
onNodeInitialized: null
}, options);
return this.each(function() {
var el = $(this), tree;
if (force || el.data("treetable") === undefined) {
tree = new Tree(this, settings);
tree.loadRows(this.rows).render();
el.addClass("treetable").data("treetable", tree);
if (settings.onInitialized != null) {
settings.onInitialized.apply(tree);
}
}
return el;
});
},
destroy: function() {
return this.each(function() {
return $(this).removeData("treetable").removeClass("treetable");
});
},
collapseAll: function() {
this.data("treetable").collapseAll();
return this;
},
collapseNode: function(id) {
var node = this.data("treetable").tree[id];
if (node) {
node.collapse();
} else {
throw new Error("Unknown node '" + id + "'");
}
return this;
},
expandAll: function() {
this.data("treetable").expandAll();
return this;
},
expandNode: function(id) {
var node = this.data("treetable").tree[id];
if (node) {
if (!node.initialized) {
node._initialize();
}
node.expand();
} else {
throw new Error("Unknown node '" + id + "'");
}
return this;
},
loadBranch: function(node, rows) {
var settings = this.data("treetable").settings,
tree = this.data("treetable").tree;
// TODO Switch to $.parseHTML
rows = $(rows);
if (node == null) { // Inserting new root nodes
this.append(rows);
} else {
var lastNode = this.data("treetable").findLastNode(node);
rows.insertAfter(lastNode.row);
}
this.data("treetable").loadRows(rows);
// Make sure nodes are properly initialized
rows.filter("tr").each(function() {
tree[$(this).data(settings.nodeIdAttr)].show();
});
if (node != null) {
// Re-render parent to ensure expander icon is shown (#79)
node.render().expand();
}
return this;
},
move: function(nodeId, destinationId) {
var destination, node;
node = this.data("treetable").tree[nodeId];
destination = this.data("treetable").tree[destinationId];
this.data("treetable").move(node, destination);
return this;
},
node: function(id) {
return this.data("treetable").tree[id];
},
removeNode: function(id) {
var node = this.data("treetable").tree[id];
if (node) {
this.data("treetable").removeNode(node);
} else {
throw new Error("Unknown node '" + id + "'");
}
return this;
},
reveal: function(id) {
var node = this.data("treetable").tree[id];
if (node) {
node.reveal();
} else {
throw new Error("Unknown node '" + id + "'");
}
return this;
},
sortBranch: function(node, columnOrFunction) {
var settings = this.data("treetable").settings,
prepValue,
sortFun;
columnOrFunction = columnOrFunction || settings.column;
sortFun = columnOrFunction;
if ($.isNumeric(columnOrFunction)) {
sortFun = function(a, b) {
var extractValue, valA, valB;
extractValue = function(node) {
var val = node.row.find("td:eq(" + columnOrFunction + ")").text();
// Ignore trailing/leading whitespace and use uppercase values for
// case insensitive ordering
return $.trim(val).toUpperCase();
}
valA = extractValue(a);
valB = extractValue(b);
if (valA < valB) return -1;
if (valA > valB) return 1;
return 0;
};
}
this.data("treetable").sortBranch(node, sortFun);
return this;
},
unloadBranch: function(node) {
this.data("treetable").unloadBranch(node);
return this;
}
};
$.fn.treetable = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
return $.error("Method " + method + " does not exist on jQuery.treetable");
}
};
// Expose classes to world
this.TreeTable || (this.TreeTable = {});
this.TreeTable.Node = Node;
this.TreeTable.Tree = Tree;
}).call(this);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,95 @@
"use strict";
function layerBtnsInit() {
/* Remove any current bindings to avoid duplicated binds */
$(".layerbtn").unbind('click');
$(".layerbtn").click(function (){
var layerObj = $(this).data("layer");
var add = ($(this).data('directive') === "add");
var thisBtn = $(this);
libtoaster.addRmLayer(layerObj, add, function (layerDepsList){
libtoaster.showChangeNotification(libtoaster.makeLayerAddRmAlertMsg(layerObj, layerDepsList, add));
/* In-cell notification */
var notification = $('<div id="temp-inline-notify" style="display: none; font-size: 11px; line-height: 1.3;" class="tooltip-inner"></div>');
thisBtn.parent().append(notification);
if (add){
if (layerDepsList.length > 0)
notification.text(String(layerDepsList.length + 1) + " layers added");
else
notification.text("1 layer added");
var layerBtnsFadeOut = $();
var layerExistsBtnFadeIn = $();
layerBtnsFadeOut = layerBtnsFadeOut.add(".layer-add-" + layerObj.id);
layerExistsBtnFadeIn = layerExistsBtnFadeIn.add(".layer-exists-" + layerObj.id);
for (var i in layerDepsList){
layerBtnsFadeOut = layerBtnsFadeOut.add(".layer-add-" + layerDepsList[i].id);
layerExistsBtnFadeIn = layerExistsBtnFadeIn.add(".layer-exists-" + layerDepsList[i].id);
}
layerBtnsFadeOut.fadeOut().promise().done(function(){
notification.fadeIn().delay(500).fadeOut(function(){
/* Fade in the buttons */
layerExistsBtnFadeIn.fadeIn();
notification.remove();
});
});
} else {
notification.text("1 layer removed");
/* Deleting a layer we only hanlde the one button */
thisBtn.fadeOut(function(){
notification.fadeIn().delay(500).fadeOut(function(){
$(".layer-add-" + layerObj.id).fadeIn();
notification.remove();
});
});
}
});
});
$("td .build-recipe-btn").unbind('click');
$("td .build-recipe-btn").click(function(e){
e.preventDefault();
var recipe = $(this).data('recipe-name');
libtoaster.startABuild(null, recipe,
function(){
/* Success */
window.location.replace(libtoaster.ctx.projectBuildsUrl);
});
});
$("td .set-default-recipe-btn").unbind('click');
$("td .set-default-recipe-btn").click(function(e){
e.preventDefault();
var recipe = $(this).data('recipe-name');
libtoaster.setDefaultImage(null, recipe,
function(){
/* Success */
window.location.replace(libtoaster.ctx.projectSpecificPageUrl);
});
});
$(".customise-btn").unbind('click');
$(".customise-btn").click(function(e){
e.preventDefault();
var imgCustomModal = $("#new-custom-image-modal");
if (imgCustomModal.length == 0)
throw("Modal new-custom-image not found");
var recipe = {id: $(this).data('recipe'), name: null}
newCustomImageModalSetRecipes([recipe]);
imgCustomModal.modal('show');
});
}

View File

@@ -0,0 +1,98 @@
/*
* layer: Object representing the parent layer { id: .. name: ... url }
* dependencies: array of dependency layer objects { id: .. name: ..}
* title: optional override for title
* body: optional override for body
* addToProject: Whether to add layers to project on accept
* successAdd: function to run on success
*/
function showLayerDepsModal(layer,
dependencies,
title,
body,
addToProject,
successAdd) {
if ($("#dependencies-modal").length === 0) {
$.get(libtoaster.ctx.htmlUrl + "/layer_deps_modal.html", function(html){
$("body").append(html);
setupModal();
});
} else {
setupModal();
}
function setupModal(){
if (title) {
$('#dependencies-modal #title').text(title);
} else {
$('#dependencies-modal #title').text(layer.name);
}
if (body) {
$("#dependencies-modal #body-text").html(body);
} else {
$("#dependencies-modal #layer-name").text(layer.name);
}
var deplistHtml = "";
for (var i = 0; i < dependencies.length; i++) {
deplistHtml += "<li><div class=\"checkbox\"><label><input name=\"dependencies\" value=\"";
deplistHtml += dependencies[i].id;
deplistHtml +="\" type=\"checkbox\" checked=\"checked\"/>";
deplistHtml += dependencies[i].name;
deplistHtml += "</label></div></li>";
}
$('#dependencies-list').html(deplistHtml);
$("#dependencies-modal").data("deps", dependencies);
/* Clear any alert notifications before showing the modal */
$(".alert").fadeOut(function(){
$('#dependencies-modal').modal('show');
});
/* Discard the old submission function */
$("#dependencies-modal-form").unbind('submit');
$("#dependencies-modal-form").submit(function (e) {
e.preventDefault();
var selectedLayerIds = [];
var selectedLayers = [];
$("input[name='dependencies']:checked").each(function () {
selectedLayerIds.push(parseInt($(this).val()));
});
/* -1 is a special dummy Id which we use when the layer isn't yet in the
* system, normally we would add the current layer to the selection.
*/
if (layer.id != -1)
selectedLayerIds.push(layer.id);
/* Find the selected layer objects from our original list */
for (var i = 0; i < selectedLayerIds.length; i++) {
for (var j = 0; j < dependencies.length; j++) {
if (dependencies[j].id == selectedLayerIds[i]) {
selectedLayers.push(dependencies[j]);
}
}
}
if (addToProject) {
libtoaster.editCurrentProject({ 'layerAdd': selectedLayerIds.join(",") }, function () {
if (successAdd) {
successAdd(selectedLayers);
}
}, function () {
console.warn("Adding layers to project failed");
});
} else {
successAdd(selectedLayers);
}
$('#dependencies-modal').modal('hide');
});
}
}

View File

@@ -0,0 +1,522 @@
"use strict";
function layerDetailsPageInit (ctx) {
var layerDepInput = $("#layer-dep-input");
var layerDepBtn = $("#add-layer-dependency-btn");
var layerDepsList = $("#layer-deps-list");
var currentLayerDepSelection;
var addRmLayerBtn = $("#add-remove-layer-btn");
var targetTab = $("#targets-tab");
var machineTab = $("#machines-tab");
var detailsTab = $("#details-tab");
var editLayerSource = $("#edit-layer-source");
var saveSourceChangesBtn = $("#save-changes-for-switch");
var layerGitRefInput = $("#layer-git-ref");
var layerSubDirInput = $('#layer-subdir');
targetTab.on('show.bs.tab', targetsTabShow);
detailsTab.on('show.bs.tab', detailsTabShow);
machineTab.on('show.bs.tab', machinesTabShow);
/* setup the dependencies typeahead */
libtoaster.makeTypeahead(layerDepInput,
libtoaster.ctx.layersTypeAheadUrl,
{ include_added: "true" }, function(item){
currentLayerDepSelection = item;
layerDepBtn.removeAttr("disabled");
});
/* disable the add layer button if its input field is empty */
layerDepInput.on("keyup",function(){
if ($(this).val().length === 0) {
layerDepBtn.attr("disabled", "disabled");
}
});
function addRemoveDep(depLayerId, add, doneCb) {
var data = { layer_version_id : ctx.layerVersion.id };
if (add)
data.add_dep = depLayerId;
else
data.rm_dep = depLayerId;
$.ajax({
type: "POST",
url: ctx.xhrUpdateLayerUrl,
data: data,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error != "ok") {
console.warn(data.error);
} else {
doneCb();
}
},
error: function (data) {
console.warn("Call failed");
console.warn(data);
}
});
}
function layerDepRemoveClick() {
var toRemove = $(this).parent().data('layer-id');
var layerDepItem = $(this);
addRemoveDep(toRemove, false, function(){
layerDepItem.parent().fadeOut(function (){
layerDepItem.remove();
});
});
}
/* Add dependency layer button click handler */
layerDepBtn.click(function(){
if (currentLayerDepSelection === undefined)
return;
addRemoveDep(currentLayerDepSelection.id, true, function(){
/* Make a list item for the new layer dependency */
var newLayerDep = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>");
newLayerDep.data('layer-id', currentLayerDepSelection.id);
newLayerDep.children("span").tooltip();
var link = newLayerDep.children("a");
link.attr("href", currentLayerDepSelection.layerdetailurl);
link.text(currentLayerDepSelection.name);
link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"});
/* Connect up the tash icon */
var trashItem = newLayerDep.children("span");
trashItem.click(layerDepRemoveClick);
layerDepsList.append(newLayerDep);
/* Clear the current selection */
layerDepInput.val("");
currentLayerDepSelection = undefined;
layerDepBtn.attr("disabled", "disabled");
});
});
$(".glyphicon-edit").click(function (){
var mParent = $(this).parent("dd");
mParent.prev().css("margin-top", "10px");
mParent.children("form").slideDown();
var currentVal = mParent.children(".current-value");
currentVal.hide();
/* Set the current value to the input field */
mParent.find("textarea,input").val(currentVal.text());
/* If the input field is empty, disable the submit button */
if ( mParent.find("textarea,input").val().length == 0 ) {
mParent.find(".change-btn").attr("disabled", "disabled");
}
/* Hides the "Not set" text */
mParent.children(".text-muted").hide();
/* We're editing so hide the delete icon */
mParent.children(".delete-current-value").hide();
mParent.find(".cancel").show();
$(this).hide();
});
$(".delete-current-value").click(function(){
var mParent = $(this).parent("dd");
mParent.find("input").val("");
mParent.find("textarea").val("");
mParent.find(".change-btn").click();
});
$(".cancel").click(function(){
var mParent = $(this).parents("dd");
$(this).hide();
mParent.children("form").slideUp(function(){
mParent.children(".current-value").show();
/* Show the "Not set" text if we ended up with no value */
if (!mParent.children(".current-value").html()){
mParent.children(".text-muted").fadeIn();
mParent.children(".delete-current-value").hide();
} else {
mParent.children(".delete-current-value").show();
}
mParent.children(".glyphicon-edit").show();
mParent.prev().css("margin-top", "0");
});
});
function defaultAddBtnText(){
var text = " Add the "+ctx.layerVersion.name+" layer to your project";
addRmLayerBtn.text(text);
addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-plus\"></span>");
addRmLayerBtn.removeClass("btn-danger");
}
function detailsTabShow(){
if (!ctx.layerVersion.inCurrentPrj)
defaultAddBtnText();
window.location.hash = "information";
}
function targetsTabShow(){
if (!ctx.layerVersion.inCurrentPrj){
if (ctx.numTargets > 0) {
var text = " Add the "+ctx.layerVersion.name+" layer to your project "+
"to enable these recipes";
addRmLayerBtn.text(text);
addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-plus\"></span>");
} else {
defaultAddBtnText();
}
}
window.location.hash = "recipes";
}
$("#recipestable").on('table-done', function(e, total, tableParams){
ctx.numTargets = total;
if (total === 0 && !tableParams.search) {
$("#no-recipes-yet").show();
} else {
$("#no-recipes-yet").hide();
}
targetTab.removeClass("text-muted");
if (window.location.hash === "#recipes"){
/* re run the machinesTabShow to update the text */
targetsTabShow();
}
});
$("#machinestable").on('table-done', function(e, total, tableParams){
ctx.numMachines = total;
if (total === 0 && !tableParams.search)
$("#no-machines-yet").show();
else
$("#no-machines-yet").hide();
machineTab.removeClass("text-muted");
if (window.location.hash === "#machines"){
/* re run the machinesTabShow to update the text */
machinesTabShow();
}
$(".select-machine-btn").click(function(e){
if ($(this).hasClass("disabled"))
e.preventDefault();
});
});
function machinesTabShow(){
if (!ctx.layerVersion.inCurrentPrj) {
if (ctx.numMachines > 0){
var text = " Add the "+ctx.layerVersion.name+" layer to your project " +
"to enable these machines";
addRmLayerBtn.text(text);
addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-plus\"></span>");
} else {
defaultAddBtnText();
}
}
window.location.hash = "machines";
}
$(".pagesize").change(function(){
var search = libtoaster.parseUrlParams();
search.limit = this.value;
window.location.search = libtoaster.dumpsUrlParams(search);
});
/* Enables the Build target and Select Machine buttons and switches the
* add/remove button
*/
function setLayerInCurrentPrj(added) {
ctx.layerVersion.inCurrentPrj = added;
if (added){
/* enable and switch all the button states */
$(".build-recipe-btn").removeClass("disabled");
$(".select-machine-btn").removeClass("disabled");
addRmLayerBtn.addClass("btn-danger");
addRmLayerBtn.data('directive', "remove");
addRmLayerBtn.text(" Remove the "+ctx.layerVersion.name+" layer from your project");
addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-trash\"></span>");
} else {
/* disable and switch all the button states */
$(".build-recipe-btn").addClass("disabled");
$(".select-machine-btn").addClass("disabled");
addRmLayerBtn.removeClass("btn-danger");
addRmLayerBtn.data('directive', "add");
/* "special" handler so that we get the correct button text which depends
* on which tab is currently visible. Unfortunately we can't just call
* tab('show') as if it's already visible it doesn't run the event.
*/
switch ($(".nav-tabs .active a").prop('id')){
case 'machines-tab':
machinesTabShow();
break;
case 'targets-tab':
targetsTabShow();
break;
default:
defaultAddBtnText();
break;
}
}
}
$("#dismiss-alert").click(function(){
$(this).parent().fadeOut();
});
/* Add or remove this layer from the project */
addRmLayerBtn.click(function() {
var add = ($(this).data('directive') === "add");
libtoaster.addRmLayer(ctx.layerVersion, add, function (layersList){
var alertMsg = $("#alert-msg");
alertMsg.html(libtoaster.makeLayerAddRmAlertMsg(ctx.layerVersion, layersList, add));
setLayerInCurrentPrj(add);
libtoaster.showChangeNotification(alertMsg);
});
});
/* Handler for all of the Change buttons */
$(".change-btn").click(function(){
var mParent = $(this).parent();
var prop = $(this).data('layer-prop');
/* We have inputs, select and textareas to potentially grab the value
* from.
*/
var entryElement = mParent.find("input");
if (entryElement.length === 0)
entryElement = mParent.find("textarea");
if (entryElement.length === 0) {
console.warn("Could not find element to get data from for this change");
return;
}
var data = { layer_version_id: ctx.layerVersion.id };
data[prop] = entryElement.val();
$.ajax({
type: "POST",
url: ctx.xhrUpdateLayerUrl,
data: data,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error != "ok") {
console.warn(data.error);
} else {
/* success layer property changed */
var inputArea = mParent.parents("dd");
var text;
text = entryElement.val();
/* Hide the "Not set" text if it's visible */
inputArea.find(".text-muted").hide();
inputArea.find(".current-value").text(text);
/* Same behaviour as cancel in that we hide the form/show current
* value.
*/
inputArea.find(".cancel").click();
}
},
error: function (data) {
console.warn("Call failed");
console.warn(data);
}
});
});
/* Disable the change button when we have no data in the input */
$("dl input, dl textarea").on("input",function() {
if ($(this).val().length === 0)
$(this).parent().next(".change-btn").attr("disabled", "disabled");
else
$(this).parent().next(".change-btn").removeAttr("disabled");
});
/* This checks to see if the dt's dd has data in it or if the change data
* form is visible, otherwise hide it
*/
$("dl").children().each(function (){
if ($(this).is("dt")) {
var dd = $(this).next("dd");
if (!dd.children("form:visible")|| !dd.find(".current-value").html()){
if (ctx.layerVersion.layer_source == ctx.layerSourceTypes.TYPE_IMPORTED ||
ctx.layerVersion.layer_source == ctx.layerSourceTypes.TYPE_LOCAL) {
/* There's no current value and the layer is editable
* so show the "Not set" and hide the delete icon
*/
dd.find(".text-muted").show();
dd.find(".delete-current-value").hide();
} else {
/* We're not viewing an editable layer so hide the empty dd/dl pair */
$(this).hide();
dd.hide();
}
}
}
});
/* Hide the right column if it contains no information */
if ($("dl.item-info").children(':visible').length === 0) {
$("dl.item-info").parent().hide();
}
/* Clear the current search selection and reload the results */
$(".target-search-clear").click(function(){
$("#target-search").val("");
$(this).parents("form").submit();
});
$(".machine-search-clear").click(function(){
$("#machine-search").val("");
$(this).parents("form").submit();
});
$("#layer-delete-confirmed").click(function(){
$("#delete-layer-modal button[data-dismiss='modal']").hide();
var message = $('<span>You have deleted <strong>1</strong> layer from your project: <strong id="deleted-layer-name"></strong>');
message.find("#deleted-layer-name").text(ctx.layerVersion.name);
$.ajax({
type: "DELETE",
url: ctx.xhrUpdateLayerUrl,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function(data) {
if (data.error != "ok") {
console.warn(data.error);
} else {
libtoaster.setNotification("layer-deleted", message.html());
window.location.replace(data.gotoUrl);
}
},
error: function(data) {
console.warn("Call failed");
console.warn(data);
}
});
});
layerDepsList.find(".glyphicon-trash").click(layerDepRemoveClick);
layerDepsList.find("a").tooltip();
$(".glyphicon-trash").tooltip();
$(".commit").tooltip();
editLayerSource.click(function() {
/* Kindly bring the git layers imported from layerindex to normal page
* and not this new page :(
*/
$(this).hide();
saveSourceChangesBtn.attr("disabled", "disabled");
$("#git-repo-info, #directory-info").hide();
$("#edit-layer-source-form").fadeIn();
if ($("#layer-dir-path-in-details").val() == "") {
//Local dir path is empty...
$("#repo").prop("checked", true);
$("#layer-git").fadeIn();
$("#layer-dir").hide();
} else {
$("#layer-git").hide();
$("#layer-dir").fadeIn();
}
});
$('input:radio[name="source-location"]').change(function() {
if ($('input[name=source-location]:checked').val() == "repo") {
$("#layer-git").fadeIn();
$("#layer-dir").hide();
if ($("#layer-git-repo-url").val().length === 0 && layerGitRefInput.val().length === 0) {
saveSourceChangesBtn.attr("disabled", "disabled");
}
} else {
$("#layer-dir").fadeIn();
$("#layer-git").hide();
}
});
$("#layer-dir-path-in-details").keyup(function() {
saveSourceChangesBtn.removeAttr("disabled");
});
$("#layer-git-repo-url").keyup(function() {
if ($("#layer-git-repo-url").val().length > 0 && layerGitRefInput.val().length > 0) {
saveSourceChangesBtn.removeAttr("disabled");
}
});
layerGitRefInput.keyup(function() {
if ($("#layer-git-repo-url").val().length > 0 && layerGitRefInput.val().length > 0) {
saveSourceChangesBtn.removeAttr("disabled");
}
});
layerSubDirInput.keyup(function(){
if ($(this).val().length > 0){
saveSourceChangesBtn.removeAttr("disabled");
}
});
$('#cancel-changes-for-switch').click(function() {
$("#edit-layer-source-form").hide();
$("#directory-info, #git-repo-info").fadeIn();
editLayerSource.show();
});
saveSourceChangesBtn.click(function() {
var layerData = {
vcs_url: $('#layer-git-repo-url').val(),
commit: layerGitRefInput.val(),
dirpath: layerSubDirInput.val(),
local_source_dir: $('#layer-dir-path-in-details').val(),
};
if ($('input[name=source-location]:checked').val() == "repo") {
layerData.local_source_dir = "";
} else {
layerData.vcs_url = "";
layerData.git_ref = "";
}
$.ajax({
type: "POST",
url: ctx.xhrUpdateLayerUrl,
data: layerData,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error != "ok") {
console.warn(data.error);
} else {
/* success layer property changed */
window.location.reload();
}
},
error: function (data) {
console.warn("Call failed");
console.warn(data);
}
});
});
}

View File

@@ -0,0 +1,841 @@
"use strict";
/* All shared functionality to go in libtoaster object.
* This object really just helps readability since we can then have
* a traceable namespace.
*/
var libtoaster = (function () {
// prevent conflicts with Bootstrap 2's typeahead (required during
// transition from v2 to v3)
var typeahead = jQuery.fn.typeahead.noConflict();
jQuery.fn._typeahead = typeahead;
/* Make a typeahead from an input element
*
* _makeTypeahead parameters
* jQElement: input element as selected by $('selector')
* xhrUrl: the url to get the JSON from; this URL should return JSON in the
* format:
* { "results": [ { "name": "test", "detail" : "a test thing" }, ... ] }
* xhrParams: the data/parameters to pass to the getJSON url e.g.
* { 'type' : 'projects' }; the text typed will be passed as 'search'.
* selectedCB: function to call once an item has been selected; has
* signature selectedCB(item), where item is an item in the format shown
* in the JSON list above, i.e.
* { "name": "name", "detail": "detail" }.
*/
function _makeTypeahead(jQElement, xhrUrl, xhrParams, selectedCB) {
if (!xhrUrl || xhrUrl.length === 0) {
throw("No url supplied for typeahead");
}
var xhrReq;
jQElement._typeahead(
{
highlight: true,
classNames: {
open: "dropdown-menu",
cursor: "active"
}
},
{
source: function (query, syncResults, asyncResults) {
xhrParams.search = query;
// if we have a request in progress, cancel it and start another
if (xhrReq) {
xhrReq.abort();
}
xhrReq = $.getJSON(xhrUrl, xhrParams, function (data) {
if (data.error !== "ok") {
console.error("Error getting data from server: " + data.error);
return;
}
xhrReq = null;
asyncResults(data.results);
});
},
// how the selected item is shown in the input
display: function (item) {
return item.name;
},
templates: {
// how the item is displayed in the dropdown
suggestion: function (item) {
var elt = document.createElement("div");
elt.innerHTML = item.name + " " + item.detail;
return elt;
}
}
}
);
// when an item is selected using the typeahead, invoke the callback
jQElement.on("typeahead:select", function (event, item) {
selectedCB(item);
});
}
/* startABuild:
* url: xhr_buildrequest or null for current project
* targets: an array or space separated list of targets to build
* onsuccess: callback for successful execution
* onfail: callback for failed execution
*/
function _startABuild (url, targets, onsuccess, onfail) {
if (!url)
url = libtoaster.ctx.xhrBuildRequestUrl;
/* Flatten the array of targets into a space spearated list */
if (targets instanceof Array){
targets = targets.reduce(function(prevV, nextV){
return prev + ' ' + next;
});
}
$.ajax( {
type: "POST",
url: url,
data: { 'targets' : targets },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
console.warn(_data.error);
} else {
if (onsuccess !== undefined) onsuccess(_data);
}
},
error: function (_data) {
console.warn("Call failed");
console.warn(_data);
if (onfail) onfail(data);
} });
}
/* cancelABuild:
* url: xhr_buildrequest url or null for current project
* buildRequestIds: space separated list of build request ids
* onsuccess: callback for successful execution
* onfail: callback for failed execution
*/
function _cancelABuild(url, buildRequestIds, onsuccess, onfail){
if (!url)
url = libtoaster.ctx.xhrBuildRequestUrl;
$.ajax( {
type: "POST",
url: url,
data: { 'buildCancel': buildRequestIds },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
console.warn(_data.error);
} else {
if (onsuccess) onsuccess(_data);
}
},
error: function (_data) {
console.warn("Call failed");
console.warn(_data);
if (onfail) onfail(_data);
}
});
}
function _getMostRecentBuilds(url, onsuccess, onfail) {
$.ajax({
url: url,
type: 'GET',
data : {format: 'json'},
headers: {'X-CSRFToken': $.cookie('csrftoken')},
success: function (data) {
onsuccess ? onsuccess(data) : console.log(data);
},
error: function (data) {
onfail ? onfail(data) : console.error(data);
}
});
}
/* Get a project's configuration info */
function _getProjectInfo(url, onsuccess, onfail){
$.ajax({
type: "GET",
url: url,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
console.warn(_data.error);
} else {
if (onsuccess !== undefined) onsuccess(_data);
}
},
error: function (_data) {
console.warn(_data);
if (onfail) onfail(_data);
}
});
}
/* Properties for data can be:
* layerDel (csv)
* layerAdd (csv)
* projectName
* projectVersion
* machineName
*/
function _editCurrentProject(data, onSuccess, onFail){
$.ajax({
type: "POST",
url: libtoaster.ctx.xhrProjectUrl,
data: data,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error != "ok") {
console.log(data.error);
if (onFail !== undefined)
onFail(data);
} else {
if (onSuccess !== undefined)
onSuccess(data);
}
},
error: function (data) {
console.log("Call failed");
console.log(data);
}
});
}
function _getLayerDepsForProject(url, onSuccess, onFail){
/* Check for dependencies not in the current project */
$.getJSON(url,
{ format: 'json' },
function(data) {
if (data.error != "ok") {
console.log(data.error);
if (onFail !== undefined)
onFail(data);
} else {
var deps = {};
/* Filter out layer dep ids which are in the
* project already.
*/
deps.list = data.layerdeps.list.filter(function(layerObj){
return (data.projectlayers.lastIndexOf(layerObj.id) < 0);
});
onSuccess(deps);
}
}, function() {
console.log("E: Failed to make request");
});
}
/* parses the query string of the current window.location to an object */
function _parseUrlParams() {
var string = window.location.search;
string = string.substr(1);
var stringArray = string.split ("&");
var obj = {};
for (var i in stringArray) {
var keyVal = stringArray[i].split ("=");
obj[keyVal[0]] = keyVal[1];
}
return obj;
}
/* takes a flat object and outputs it as a query string
* e.g. the output of dumpsUrlParams
*/
function _dumpsUrlParams(obj) {
var str = "?";
for (var key in obj){
if (!obj[key])
continue;
str += key+ "="+obj[key].toString();
str += "&";
}
/* Maintain the current hash */
str += window.location.hash;
return str;
}
function _addRmLayer(layerObj, add, doneCb){
if (layerObj.xhrLayerUrl === undefined){
alert("ERROR: missing xhrLayerUrl object. Please file a bug.");
return;
}
if (add === true) {
/* If adding get the deps for this layer */
libtoaster.getLayerDepsForProject(layerObj.xhrLayerUrl,
function (layers) {
/* got result for dependencies */
if (layers.list.length === 0){
var editData = { layerAdd : layerObj.id };
libtoaster.editCurrentProject(editData, function() {
doneCb([]);
});
return;
} else {
try {
showLayerDepsModal(layerObj, layers.list, null, null, true, doneCb);
} catch (e) {
$.getScript(libtoaster.ctx.jsUrl + "layerDepsModal.js", function(){
showLayerDepsModal(layerObj, layers.list, null, null, true, doneCb);
}, function(){
console.warn("Failed to load layerDepsModal");
});
}
}
}, null);
} else if (add === false) {
var editData = { layerDel : layerObj.id };
libtoaster.editCurrentProject(editData, function () {
doneCb([]);
}, function () {
console.warn ("Removing layer from project failed");
doneCb(null);
});
}
}
function _makeLayerAddRmAlertMsg(layer, layerDepsList, add) {
var alertMsg;
if (layerDepsList.length > 0 && add === true) {
alertMsg = $("<span>You have added <strong>"+(layerDepsList.length+1)+"</strong> layers to your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a> and its dependencies </span>");
/* Build the layer deps list */
layerDepsList.map(function(layer, i){
var link = $("<a class=\"alert-link\"></a>");
link.attr("href", layer.layerdetailurl);
link.text(layer.name);
link.tooltip({title: layer.tooltip});
if (i !== 0)
alertMsg.append(", ");
alertMsg.append(link);
});
} else if (layerDepsList.length === 0 && add === true) {
alertMsg = $("<span>You have added <strong>1</strong> layer to your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a></span></span>");
} else if (add === false) {
alertMsg = $("<span>You have removed <strong>1</strong> layer from your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a></span>");
}
alertMsg.children("#layer-affected-name").text(layer.name);
alertMsg.children("#layer-affected-name").attr("href", layer.layerdetailurl);
return alertMsg.html();
}
function _showChangeNotification(message){
$(".alert-dismissible").fadeOut().promise().done(function(){
var alertMsg = $("#change-notification-msg");
alertMsg.html(message);
$("#change-notification, #change-notification *").fadeIn();
});
}
function _createCustomRecipe(name, baseRecipeId, doneCb){
var data = {
'name' : name,
'project' : libtoaster.ctx.projectId,
'base' : baseRecipeId,
};
$.ajax({
type: "POST",
url: libtoaster.ctx.xhrCustomRecipeUrl,
data: data,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (ret) {
if (doneCb){
doneCb(ret);
} else if (ret.error !== "ok") {
console.warn(ret.error);
}
},
error: function (ret) {
console.warn("Call failed");
console.warn(ret);
}
});
}
/* Validate project names. Use unique project names
All arguments accepted by this function are JQeury objects.
For example if the HTML element has "hint-error-project-name", then
it is passed to this function as $("#hint-error-project-name").
Arg1 - projectName : This is a string object. In the HTML, project name will be entered here.
Arg2 - hintEerror : This is a jquery object which will accept span which throws error for
duplicate project
Arg3 - ctrlGrpValidateProjectName : This object holds the div with class "control-group"
Arg4 - enableOrDisableBtn : This object will help the API to enable or disable the form.
For example in the new project the create project button will be hidden if the
duplicate project exist. Similarly in the projecttopbar the save button will be
disabled if the project name already exist.
Return - This function doesn't return anything. It sets/unsets the behavior of the elements.
*/
function _makeProjectNameValidation(projectName, hintError,
ctrlGrpValidateProjectName, enableOrDisableBtn ) {
function checkProjectName(projectName){
$.ajax({
type: "GET",
url: libtoaster.ctx.projectsTypeAheadUrl,
data: { 'search' : projectName },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function(data){
if (data.results.length > 0 &&
data.results[0].name === projectName) {
// This project name exists hence show the error and disable
// the save button
ctrlGrpValidateProjectName.addClass('has-error');
hintError.show();
enableOrDisableBtn.attr('disabled', 'disabled');
} else {
ctrlGrpValidateProjectName.removeClass('has-error');
hintError.hide();
enableOrDisableBtn.removeAttr('disabled');
}
},
error: function (data) {
console.log(data);
},
});
}
/* The moment user types project name remove the error */
projectName.on("input", function() {
var projectName = $(this).val();
checkProjectName(projectName)
});
/* Validate new project name */
projectName.on("blur", function(){
var projectName = $(this).val();
checkProjectName(projectName)
});
}
// if true, the loading spinner for Ajax requests will be displayed
// if requests take more than 1200ms
var ajaxLoadingTimerEnabled = true;
// turn on the page-level loading spinner for Ajax requests
function _enableAjaxLoadingTimer() {
ajaxLoadingTimerEnabled = true;
}
// turn off the page-level loading spinner for Ajax requests
function _disableAjaxLoadingTimer() {
ajaxLoadingTimerEnabled = false;
}
/* Utility function to set a notification for the next page load */
function _setNotification(name, message){
var data = {
name: name,
message: message
};
$.cookie('toaster-notification', JSON.stringify(data), { path: '/'});
}
/* _updateProject:
* url: xhrProjectUpdateUrl or null for current project
* onsuccess: callback for successful execution
* onfail: callback for failed execution
*/
function _updateProject (url, targets, default_image, onsuccess, onfail) {
if (!url)
url = libtoaster.ctx.xhrProjectUpdateUrl;
/* Flatten the array of targets into a space spearated list */
if (targets instanceof Array){
targets = targets.reduce(function(prevV, nextV){
return prev + ' ' + next;
});
}
$.ajax( {
type: "POST",
url: url,
data: { 'do_update' : 'True' , 'targets' : targets , 'default_image' : default_image , },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
console.warn(_data.error);
} else {
if (onsuccess !== undefined) onsuccess(_data);
}
},
error: function (_data) {
console.warn("Call failed");
console.warn(_data);
if (onfail) onfail(data);
} });
}
/* _cancelProject:
* url: xhrProjectUpdateUrl or null for current project
* onsuccess: callback for successful execution
* onfail: callback for failed execution
*/
function _cancelProject (url, onsuccess, onfail) {
if (!url)
url = libtoaster.ctx.xhrProjectCancelUrl;
$.ajax( {
type: "POST",
url: url,
data: { 'do_cancel' : 'True' },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
console.warn(_data.error);
} else {
if (onsuccess !== undefined) onsuccess(_data);
}
},
error: function (_data) {
console.warn("Call failed");
console.warn(_data);
if (onfail) onfail(data);
} });
}
/* _setDefaultImage:
* url: xhrSetDefaultImageUrl or null for current project
* targets: an array or space separated list of targets to set as default
* onsuccess: callback for successful execution
* onfail: callback for failed execution
*/
function _setDefaultImage (url, targets, onsuccess, onfail) {
if (!url)
url = libtoaster.ctx.xhrSetDefaultImageUrl;
/* Flatten the array of targets into a space spearated list */
if (targets instanceof Array){
targets = targets.reduce(function(prevV, nextV){
return prev + ' ' + next;
});
}
$.ajax( {
type: "POST",
url: url,
data: { 'targets' : targets },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
console.warn(_data.error);
} else {
if (onsuccess !== undefined) onsuccess(_data);
}
},
error: function (_data) {
console.warn("Call failed");
console.warn(_data);
if (onfail) onfail(data);
} });
}
return {
enableAjaxLoadingTimer: _enableAjaxLoadingTimer,
disableAjaxLoadingTimer: _disableAjaxLoadingTimer,
reload_params : reload_params,
startABuild : _startABuild,
cancelABuild : _cancelABuild,
getMostRecentBuilds: _getMostRecentBuilds,
makeTypeahead : _makeTypeahead,
getProjectInfo: _getProjectInfo,
getLayerDepsForProject : _getLayerDepsForProject,
editCurrentProject : _editCurrentProject,
debug: false,
parseUrlParams : _parseUrlParams,
dumpsUrlParams : _dumpsUrlParams,
addRmLayer : _addRmLayer,
makeLayerAddRmAlertMsg : _makeLayerAddRmAlertMsg,
showChangeNotification : _showChangeNotification,
createCustomRecipe: _createCustomRecipe,
makeProjectNameValidation: _makeProjectNameValidation,
setNotification: _setNotification,
updateProject : _updateProject,
cancelProject : _cancelProject,
setDefaultImage : _setDefaultImage,
};
})();
/* keep this in the global scope for compatability */
function reload_params(params) {
var uri = window.location.href;
var splitlist = uri.split("?");
var url = splitlist[0];
var parameters = splitlist[1];
// deserialize the call parameters
var cparams = [];
if(parameters)
cparams = parameters.split("&");
var nparams = {};
for (var i = 0; i < cparams.length; i++) {
var temp = cparams[i].split("=");
nparams[temp[0]] = temp[1];
}
// update parameter values
for (i in params) {
nparams[encodeURIComponent(i)] = encodeURIComponent(params[i]);
}
// serialize the structure
var callparams = [];
for (i in nparams) {
callparams.push(i+"="+nparams[i]);
}
window.location.href = url+"?"+callparams.join('&');
}
/* Things that happen for all pages */
$(document).ready(function() {
(function showNotificationRequest(){
var cookie = $.cookie('toaster-notification');
if (!cookie)
return;
var notificationData = JSON.parse(cookie);
libtoaster.showChangeNotification(notificationData.message);
$.removeCookie('toaster-notification', { path: "/"});
})();
var ajaxLoadingTimer;
/* If we don't have a console object which might be the case in some
* browsers, no-op it to avoid undefined errors.
*/
if (!window.console) {
window.console = {};
window.console.warn = function() {};
window.console.error = function() {};
}
/*
* highlight plugin.
*/
hljs.initHighlightingOnLoad();
// Prevent invalid links from jumping page scroll
$('a[href="#"]').click(function() {
return false;
});
/* START TODO Delete this section now redundant */
/* Belen's additions */
// turn Edit columns dropdown into a multiselect menu
$('.dropdown-menu input, .dropdown-menu label').click(function(e) {
e.stopPropagation();
});
// enable popovers in any table cells that contain an anchor with the
// .btn class applied, and make sure popovers work on click, are mutually
// exclusive and they close when your click outside their area
$('html').click(function(){
$('td > a.btn').popover('hide');
});
$('td > a.btn').popover({
html:true,
placement:'left',
container:'body',
trigger:'manual'
}).click(function(e){
$('td > a.btn').not(this).popover('hide');
// ideally we would use 'toggle' here
// but it seems buggy in our Bootstrap version
$(this).popover('show');
e.stopPropagation();
});
// enable tooltips for applied filters
$('th a.btn-primary').tooltip({container:'body', html:true, placement:'bottom', delay:{hide:1500}});
// hide applied filter tooltip when you click on the filter button
$('th a.btn-primary').click(function () {
$('.tooltip').hide();
});
/* Initialise bootstrap tooltips */
$(".get-help, [data-toggle=tooltip]").tooltip({
container : 'body',
html : true,
delay: { show : 300 }
});
// show help bubble on hover inside tables
$("table").on("mouseover", "th, td", function () {
$(this).find(".hover-help").css("visibility","visible");
});
$("table").on("mouseleave", "th, td", function () {
$(this).find(".hover-help").css("visibility","hidden");
});
/* END TODO Delete this section now redundant */
// show task type and outcome in task details pages
$(".task-info").tooltip({ container: 'body', html: true, delay: {show: 200}, placement: 'right' });
// initialise the tooltips for the edit icons
$(".glyphicon-edit").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" });
// initialise the tooltips for the download icons
$(".icon-download-alt").tooltip({ container: 'body', html: true, delay: { show: 200 } });
// initialise popover for debug information
$(".glyphicon-info-sign").popover( { placement: 'bottom', html: true, container: 'body' });
// linking directly to tabs
$(function(){
var hash = window.location.hash;
$('ul.nav a[href="' + hash + '"]').tab('show');
$('.nav-tabs a').click(function () {
$(this).tab('show');
$('body').scrollTop();
});
});
// toggle for long content (variables, python stack trace, etc)
$('.full, .full-hide').hide();
$('.full-show').click(function(){
$('.full').slideDown(function(){
$('.full-hide').show();
});
$(this).hide();
});
$('.full-hide').click(function(){
$(this).hide();
$('.full').slideUp(function(){
$('.full-show').show();
});
});
//toggle the errors and warnings sections
$('.show-errors').click(function() {
$('#collapse-errors').addClass('in');
});
$('.toggle-errors').click(function() {
$('#collapse-errors').toggleClass('in');
});
$('.show-warnings').click(function() {
$('#collapse-warnings').addClass('in');
});
$('.toggle-warnings').click(function() {
$('#collapse-warnings').toggleClass('in');
});
$('.show-exceptions').click(function() {
$('#collapse-exceptions').addClass('in');
});
$('.toggle-exceptions').click(function() {
$('#collapse-exceptions').toggleClass('in');
});
$("#hide-alert").click(function(){
$(this).parent().fadeOut();
});
//show warnings section when requested from the previous page
if (location.href.search('#warnings') > -1) {
$('#collapse-warnings').addClass('in');
}
/* Show the loading notification if nothing has happend after 1.5
* seconds
*/
$(document).bind("ajaxStart", function(){
if (ajaxLoadingTimer)
window.clearTimeout(ajaxLoadingTimer);
ajaxLoadingTimer = window.setTimeout(function() {
if (libtoaster.ajaxLoadingTimerEnabled) {
$("#loading-notification").fadeIn();
}
}, 1200);
});
$(document).bind("ajaxStop", function(){
if (ajaxLoadingTimer)
window.clearTimeout(ajaxLoadingTimer);
$("#loading-notification").fadeOut();
});
$(document).ajaxError(function(event, jqxhr, settings, errMsg){
if (errMsg === 'abort')
return;
console.warn("Problem with xhr call");
console.warn(errMsg);
console.warn(jqxhr.responseText);
});
function check_for_duplicate_ids () {
/* warn about duplicate element ids */
var ids = {};
$("[id]").each(function() {
if (this.id && ids[this.id]) {
console.warn('Duplicate element id #'+this.id);
}
ids[this.id] = true;
});
}
/* Make sure we don't have a notification overlay a modal */
$(".modal").on('show.bs.modal', function(){
$(".alert-dismissible").fadeOut();
});
if (libtoaster.debug) {
check_for_duplicate_ids();
} else {
/* Debug is false so supress warnings by overriding the functions */
window.console.warn = function () {};
window.console.error = function () {};
}
});

View File

@@ -0,0 +1,152 @@
function mrbSectionInit(ctx){
$('#latest-builds').on('click', '.cancel-build-btn', function(e){
e.stopImmediatePropagation();
e.preventDefault();
var url = $(this).data('request-url');
var buildReqIds = $(this).data('buildrequest-id');
libtoaster.cancelABuild(url, buildReqIds, function () {
window.location.reload();
}, null);
});
$('#latest-builds').on('click', '.rebuild-btn', function(e){
e.stopImmediatePropagation();
e.preventDefault();
var url = $(this).data('request-url');
var target = $(this).data('target');
libtoaster.startABuild(url, target, function(){
window.location.reload();
}, null);
});
// cached version of buildData, so we can determine whether a build has
// changed since it was last fetched, and update the DOM appropriately
var buildData = {};
// returns the cached version of this build, or {} is there isn't a cached one
function getCached(build) {
return buildData[build.id] || {};
}
// returns true if a build's state changed to "Succeeded", "Failed"
// or "Cancelled" from some other value
function buildFinished(build) {
var cached = getCached(build);
return cached.state &&
cached.state !== build.state &&
(build.state == 'Succeeded' || build.state == 'Failed' ||
build.state == 'Cancelled');
}
// returns true if the state changed
function stateChanged(build) {
var cached = getCached(build);
return (cached.state !== build.state);
}
// returns true if the tasks_complete_percentage changed
function tasksProgressChanged(build) {
var cached = getCached(build);
return (cached.tasks_complete_percentage !== build.tasks_complete_percentage);
}
// returns true if the number of recipes parsed/to parse changed
function recipeProgressChanged(build) {
var cached = getCached(build);
return (cached.recipes_parsed_percentage !== build.recipes_parsed_percentage);
}
// returns true if the number of repos cloned/to clone changed
function cloneProgressChanged(build) {
var cached = getCached(build);
return (cached.repos_cloned_percentage !== build.repos_cloned_percentage);
}
function refreshMostRecentBuilds(){
libtoaster.getMostRecentBuilds(
libtoaster.ctx.mostRecentBuildsUrl,
// success callback
function (data) {
var build;
var tmpl;
var container;
var selector;
var colourClass;
var elements;
for (var i = 0; i < data.length; i++) {
build = data[i];
if (buildFinished(build)) {
// a build finished: reload the whole page so that the build
// shows up in the builds table
window.location.reload(true);
}
else if (stateChanged(build)) {
// update the whole template
build.warnings_pluralise = (build.warnings !== 1 ? 's' : '');
build.errors_pluralise = (build.errors !== 1 ? 's' : '');
tmpl = $.templates("#build-template");
html = $(tmpl.render(build));
selector = '[data-latest-build-result="' + build.id + '"] ' +
'[data-role="build-status-container"]';
container = $(selector);
// initialize bootstrap tooltips in the new HTML
html.find('span.glyphicon-question-sign').tooltip();
container.html(html);
}
else if (cloneProgressChanged(build)) {
// update the clone progress text
selector = '#repos-cloned-percentage-' + build.id;
$(selector).html(build.repos_cloned_percentage);
selector = '#repos-cloned-progressitem-' + build.id;
$(selector).html('('+build.progress_item+')');
// update the recipe progress bar
selector = '#repos-cloned-percentage-bar-' + build.id;
$(selector).width(build.repos_cloned_percentage + '%');
}
else if (tasksProgressChanged(build)) {
// update the task progress text
selector = '#build-pc-done-' + build.id;
$(selector).html(build.tasks_complete_percentage);
// update the task progress bar
selector = '#build-pc-done-bar-' + build.id;
$(selector).width(build.tasks_complete_percentage + '%');
}
else if (recipeProgressChanged(build)) {
// update the recipe progress text
selector = '#recipes-parsed-percentage-' + build.id;
$(selector).html(build.recipes_parsed_percentage);
// update the recipe progress bar
selector = '#recipes-parsed-percentage-bar-' + build.id;
$(selector).width(build.recipes_parsed_percentage + '%');
}
buildData[build.id] = build;
}
},
// fail callback
function (data) {
console.error(data);
}
);
}
window.setInterval(refreshMostRecentBuilds, 1500);
refreshMostRecentBuilds();
}

View File

@@ -0,0 +1,206 @@
"use strict";
/*
Used for the newcustomimage_modal actions
The .data('recipe') value on the outer element determines which
recipe ID is used as the basis for the new custom image recipe created via
this modal.
Use newCustomImageModalSetRecipes() to set the recipes available as a base
for the new custom image. This will manage the addition of radio buttons
to select the base image (or remove the radio buttons, if there is only a
single base image available).
*/
function newCustomImageModalInit(){
var newCustomImgBtn = $("#create-new-custom-image-btn");
var imgCustomModal = $("#new-custom-image-modal");
var invalidNameHelp = $("#invalid-name-help");
var invalidRecipeHelp = $("#invalid-recipe-help");
var nameInput = imgCustomModal.find('input');
var invalidNameMsg = "Image names cannot contain spaces or capital letters. The only allowed special character is dash (-).";
var duplicateNameMsg = "An image with this name already exists. Image names must be unique.";
var duplicateImageInProjectMsg = "An image with this name already exists in this project."
var invalidBaseRecipeIdMsg = "Please select an image to customise.";
var missingParentRecipe = "The parent recipe file was not found. Cancel this action, build any target (like 'quilt-native') to force all new layers to clone, and try again";
var unknownError = "Unexpected error: ";
// set button to "submit" state and enable text entry so user can
// enter the custom recipe name
showSubmitState();
/* capture clicks on radio buttons inside the modal; when one is selected,
* set the recipe on the modal
*/
imgCustomModal.on("click", "[name='select-image']", function(e) {
clearRecipeError();
$(".radio").each(function(){
$(this).removeClass("has-error");
});
var recipeId = $(e.target).attr('data-recipe');
imgCustomModal.data('recipe', recipeId);
});
newCustomImgBtn.click(function(e){
// disable the button and text entry
showLoadingState();
e.preventDefault();
var baseRecipeId = imgCustomModal.data('recipe');
if (!baseRecipeId) {
showRecipeError(invalidBaseRecipeIdMsg);
$(".radio").each(function(){
$(this).addClass("has-error");
});
return;
}
if (nameInput.val().length > 0) {
libtoaster.createCustomRecipe(nameInput.val(), baseRecipeId,
function(ret) {
showSubmitState();
if (ret.error !== "ok") {
console.warn(ret.error);
if (ret.error === "invalid-name") {
showNameError(invalidNameMsg);
return;
} else if (ret.error === "recipe-already-exists") {
showNameError(duplicateNameMsg);
return;
} else if (ret.error === "image-already-exists") {
showNameError(duplicateImageInProjectMsg);
return;
} else if (ret.error === "recipe-parent-not-exist") {
showNameError(missingParentRecipe);
} else {
showNameError(unknownError + ret.error);
}
} else {
imgCustomModal.modal('hide');
imgCustomModal.one('hidden.bs.modal', showSubmitState);
window.location.replace(ret.url + '?notify=new');
}
});
}
});
// enable text entry, show "Create image" button text
function showSubmitState() {
libtoaster.enableAjaxLoadingTimer();
newCustomImgBtn.find('[data-role="loading-state"]').hide();
newCustomImgBtn.find('[data-role="submit-state"]').show();
newCustomImgBtn.removeAttr('disabled');
nameInput.removeAttr('disabled');
}
// disable text entry, show "Creating image..." button text;
// we also disabled the page-level ajax loading spinner while this spinner
// is active
function showLoadingState() {
libtoaster.disableAjaxLoadingTimer();
newCustomImgBtn.find('[data-role="submit-state"]').hide();
newCustomImgBtn.find('[data-role="loading-state"]').show();
newCustomImgBtn.attr('disabled', 'disabled');
nameInput.attr('disabled', 'disabled');
}
function showNameError(text){
invalidNameHelp.text(text);
invalidNameHelp.show();
nameInput.parent().addClass('has-error');
}
function showRecipeError(text){
invalidRecipeHelp.text(text);
invalidRecipeHelp.show();
}
function clearRecipeError(){
invalidRecipeHelp.hide();
}
nameInput.on('keyup', function(){
if (nameInput.val().length === 0){
newCustomImgBtn.prop("disabled", true);
return
}
if (nameInput.val().search(/[^a-z|0-9|-]/) != -1){
showNameError(invalidNameMsg);
newCustomImgBtn.prop("disabled", true);
nameInput.parent().addClass('has-error');
} else {
invalidNameHelp.hide();
newCustomImgBtn.prop("disabled", false);
nameInput.parent().removeClass('has-error');
}
});
}
/* Set the image recipes which can used as the basis for the custom
* image recipe the user is creating
* baseRecipes: a list of one or more recipes which can be
* used as the base for the new custom image recipe in the format:
* [{'id': <recipe ID>, 'name': <recipe name>'}, ...]
*
* if recipes is a single recipe, just show the text box to set the
* name for the new custom image; if recipes contains multiple recipe objects,
* show a set of radio buttons so the user can decide which to use as the
* basis for the new custom image
*/
function newCustomImageModalSetRecipes(baseRecipes) {
var imgCustomModal = $("#new-custom-image-modal");
var imageSelector = $('#new-custom-image-modal [data-role="image-selector"]');
var imageSelectRadiosContainer = $('#new-custom-image-modal [data-role="image-selector-radios"]');
// remove any existing radio buttons + labels
imageSelector.remove('[data-role="image-radio"]');
if (baseRecipes.length === 1) {
// hide the radio button container
imageSelector.hide();
/* set the single recipe ID on the modal as it's the only one
* we can build from.
*/
imgCustomModal.data('recipe', baseRecipes[0].id);
}
else {
// add radio buttons; note that the handlers for the radio buttons
// are set in newCustomImageModalInit via event delegation
for (var i = 0; i < baseRecipes.length; i++) {
var recipe = baseRecipes[i];
imageSelectRadiosContainer.append(
'<div class="radio"><label data-role="image-radio">' +
'<input type="radio" name="select-image" ' +
'data-recipe="' + recipe.id + '">' +
recipe.name +
'</label></div>'
);
}
/* select the first radio button as default selection. Radio button
* groups should always display with an option checked
*/
imageSelectRadiosContainer.find("input:radio:first").attr("checked", "checked");
/* check which radio button is selected by default inside the modal,
* and set the recipe on the modal accordingly
*/
imageSelectRadiosContainer.find("input:radio").each(function(){
if ( $(this).is(":checked") ) {
var recipeId = $(this).attr("data-recipe");
imgCustomModal.data("recipe", recipeId);
}
});
// show the radio button container
imageSelector.show();
}
}

View File

@@ -0,0 +1,390 @@
"use strict";
function projectPageInit(ctx) {
var layerAddInput = $("#layer-add-input");
var layersInPrjList = $("#layers-in-project-list");
var layerAddBtn = $("#add-layer-btn");
var machineChangeInput = $("#machine-change-input");
var machineChangeBtn = $("#machine-change-btn");
var machineForm = $("#select-machine-form");
var machineChangeFormToggle = $("#change-machine-toggle");
var machineNameTitle = $("#project-machine-name");
var machineChangeCancel = $("#cancel-machine-change");
var machineInputForm = $("#machine-input-form");
var invalidMachineNameHelp = $("#invalid-machine-name-help");
var distroChangeInput = $("#distro-change-input");
var distroChangeBtn = $("#distro-change-btn");
var distroForm = $("#select-distro-form");
var distroChangeFormToggle = $("#change-distro-toggle");
var distroNameTitle = $("#project-distro-name");
var distroChangeCancel = $("#cancel-distro-change");
var freqBuildBtn = $("#freq-build-btn");
var freqBuildList = $("#freq-build-list");
var releaseChangeFormToggle = $("#release-change-toggle");
var releaseTitle = $("#project-release-title");
var releaseForm = $("#change-release-form");
var releaseModal = $("#change-release-modal");
var cancelReleaseChange = $("#cancel-release-change");
var currentLayerAddSelection;
var currentMachineAddSelection = "";
var currentDistroAddSelection = "";
var urlParams = libtoaster.parseUrlParams();
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
updateProjectLayers(prjInfo.layers);
updateFreqBuildRecipes(prjInfo.freqtargets);
updateProjectRelease(prjInfo.release);
/* If we're receiving a machine set from the url and it's different from
* our current machine then activate set machine sequence.
*/
if (urlParams.hasOwnProperty('setMachine') &&
urlParams.setMachine !== prjInfo.machine.name){
machineChangeInput.val(urlParams.setMachine);
machineChangeBtn.click();
} else {
updateMachineName(prjInfo.machine.name);
}
/* If we're receiving a distro set from the url and it's different from
* our current distro then activate set machine sequence.
*/
if (urlParams.hasOwnProperty('setDistro') &&
urlParams.setDistro !== prjInfo.distro.name){
distroChangeInput.val(urlParams.setDistro);
distroChangeBtn.click();
} else {
updateDistroName(prjInfo.distro?.name);
}
/* Now we're really ready show the page */
$("#project-page").show();
/* Set the project name in the delete modal */
$("#delete-project-modal .project-name").text(prjInfo.name);
});
if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new-project'){
$("#project-created-notification").show();
}
/* Add/Rm layer functionality */
libtoaster.makeTypeahead(layerAddInput, libtoaster.ctx.layersTypeAheadUrl, { include_added: "false" }, function(item){
currentLayerAddSelection = item;
layerAddBtn.removeAttr("disabled");
});
layerAddInput.keyup(function() {
if ($(this).val().length == 0) {
layerAddBtn.attr("disabled", "disabled")
}
});
layerAddBtn.click(function(e){
e.preventDefault();
var layerObj = currentLayerAddSelection;
addRmLayer(layerObj, true);
/* Reset the text input */
layerAddInput.val("");
/* Disable the add layer button*/
layerAddBtn.attr("disabled", "disabled");
});
function addRmLayer(layerObj, add){
libtoaster.addRmLayer(layerObj, add, function(layerDepsList){
if (add){
updateProjectLayers([layerObj]);
updateProjectLayers(layerDepsList);
}
/* Show the alert message */
var message = libtoaster.makeLayerAddRmAlertMsg(layerObj, layerDepsList, add);
libtoaster.showChangeNotification(message);
});
}
function updateProjectLayers(layers){
/* No layers to add */
if (layers.length === 0){
updateLayersCount();
return;
}
for (var i in layers){
var layerObj = layers[i];
var projectLayer = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Remove\"></span></li>");
projectLayer.data('layer', layerObj);
projectLayer.children("span").tooltip();
var link = projectLayer.children("a");
link.attr("href", layerObj.layerdetailurl);
link.text(layerObj.name);
if (layerObj.local_source_dir) {
link.tooltip({title: layerObj.local_source_dir, placement: "right"});
} else {
link.tooltip({title: layerObj.vcs_url + " | "+ layerObj.vcs_reference, placement: "right"});
}
var trashItem = projectLayer.children("span");
trashItem.click(function (e) {
e.preventDefault();
var layerObjToRm = $(this).parent().data('layer');
addRmLayer(layerObjToRm, false);
$(this).parent().fadeOut(function (){
$(this).remove();
updateLayersCount();
});
});
layersInPrjList.append(projectLayer);
updateLayersCount();
}
}
function updateLayersCount(){
var count = $("#layers-in-project-list").children().length;
var noLayerMsg = $("#no-layers-in-project");
var buildInput = $("#build-input");
if (count === 0) {
noLayerMsg.fadeIn();
$("#no-layers-in-project").fadeIn();
buildInput.attr("disabled", "disabled");
} else {
noLayerMsg.hide();
buildInput.removeAttr("disabled");
}
$("#project-layers-count").text(count);
return count;
}
/* Frequent builds functionality */
function updateFreqBuildRecipes(recipes) {
var noMostBuilt = $("#no-most-built");
if (recipes.length === 0){
noMostBuilt.show();
freqBuildBtn.hide();
} else {
noMostBuilt.hide();
freqBuildBtn.show();
}
for (var i in recipes){
var freqTargetCheck = $('<li><div class="checkbox"><label><input type="checkbox" /><span class="freq-target-name"></span></label></li>');
freqTargetCheck.find(".freq-target-name").text(recipes[i]);
freqTargetCheck.find("input").val(recipes[i]);
freqTargetCheck.click(function(){
if (freqBuildList.find(":checked").length > 0)
freqBuildBtn.removeAttr("disabled");
else
freqBuildBtn.attr("disabled", "disabled");
});
freqBuildList.append(freqTargetCheck);
}
}
freqBuildBtn.click(function(e){
e.preventDefault();
var toBuild = "";
freqBuildList.find(":checked").each(function(){
toBuild += $(this).val() + ' ';
});
toBuild = toBuild.trim();
libtoaster.startABuild(null, toBuild,
function(){
/* Build request started */
window.location.replace(libtoaster.ctx.projectBuildsUrl);
},
function(){
/* Build request failed */
console.warn("Build request failed to be created");
});
});
/* Change machine functionality */
machineChangeInput.keyup(function(){
if ($(this).val().indexOf(' ') >= 0) {
machineChangeBtn.attr("disabled", "disabled");
invalidMachineNameHelp.show();
machineInputForm.addClass('has-error');
} else {
machineChangeBtn.removeAttr("disabled");
invalidMachineNameHelp.hide();
machineInputForm.removeClass('has-error');
}
});
machineChangeFormToggle.click(function(){
machineChangeInput.val(machineNameTitle.text());
machineChangeBtn.removeAttr("disabled");
invalidMachineNameHelp.hide();
machineInputForm.removeClass('has-error');
machineForm.slideDown();
machineNameTitle.hide();
$(this).hide();
});
machineChangeCancel.click(function(){
machineForm.slideUp(function(){
machineNameTitle.show();
machineChangeFormToggle.show();
});
});
function updateMachineName(machineName){
machineChangeInput.val(machineName);
machineNameTitle.text(machineName);
}
libtoaster.makeTypeahead(machineChangeInput,
libtoaster.ctx.machinesTypeAheadUrl,
{ }, function(item){
currentMachineAddSelection = item.name;
machineChangeBtn.removeAttr("disabled");
});
machineChangeBtn.click(function(e){
e.preventDefault();
/* We accept any value regardless of typeahead selection or not */
if (machineChangeInput.val().length === 0)
return;
currentMachineAddSelection = machineChangeInput.val();
libtoaster.editCurrentProject(
{ machineName : currentMachineAddSelection },
function(){
/* Success machine changed */
updateMachineName(currentMachineAddSelection);
machineChangeCancel.click();
/* Show the alert message */
var message = $('<span>You have changed the machine to: <strong><span id="notify-machine-name"></span></strong></span>');
message.find("#notify-machine-name").text(currentMachineAddSelection);
libtoaster.showChangeNotification(message);
},
function(){
/* Failed machine changed */
console.warn("Failed to change machine");
});
});
/* Change distro functionality */
distroChangeFormToggle.click(function(){
distroForm.slideDown();
distroNameTitle.hide();
$(this).hide();
});
distroChangeCancel.click(function(){
distroForm.slideUp(function(){
distroNameTitle.show();
distroChangeFormToggle.show();
});
});
function updateDistroName(distroName){
distroChangeInput.val(distroName);
distroNameTitle.text(distroName);
}
libtoaster.makeTypeahead(distroChangeInput,
libtoaster.ctx.distrosTypeAheadUrl,
{ }, function(item){
currentDistroAddSelection = item.name;
distroChangeBtn.removeAttr("disabled");
});
distroChangeBtn.click(function(e){
e.preventDefault();
/* We accept any value regardless of typeahead selection or not */
if (distroChangeInput.val().length === 0)
return;
currentDistroAddSelection = distroChangeInput.val();
libtoaster.editCurrentProject(
{ distroName : currentDistroAddSelection },
function(){
/* Success machine changed */
updateDistroName(currentDistroAddSelection);
distroChangeCancel.click();
/* Show the alert message */
var message = $('<span>You have changed the distro to: <strong><span id="notify-machine-name"></span></strong></span>');
message.find("#notify-machine-name").text(currentDistroAddSelection);
libtoaster.showChangeNotification(message);
},
function(){
/* Failed machine changed */
console.warn("Failed to change distro");
});
});
/* Change release functionality */
function updateProjectRelease(release){
releaseTitle.text(release.description);
}
$("#delete-project-confirmed").click(function(e){
e.preventDefault();
libtoaster.disableAjaxLoadingTimer();
$(this).find('[data-role="submit-state"]').hide();
$(this).find('[data-role="loading-state"]').show();
$(this).attr("disabled", "disabled");
$('#delete-project-modal [data-dismiss="modal"]').hide();
$.ajax({
type: 'DELETE',
url: libtoaster.ctx.xhrProjectUrl,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error !== "ok") {
console.warn(data.error);
} else {
var msg = $('<span>You have deleted <strong>1</strong> project: <strong id="project-deleted"></strong></span>');
msg.find("#project-deleted").text(libtoaster.ctx.projectName);
libtoaster.setNotification("project-deleted", msg.html());
window.location.replace(data.gotoUrl);
}
},
error: function (data) {
console.warn(data);
}
});
});
}

View File

@@ -0,0 +1,119 @@
'use strict';
function projectTopBarInit(ctx) {
var projectNameForm = $("#project-name-change-form");
var projectNameContainer = $("#project-name-container");
var projectName = $(".project-name");
var projectNameFormToggle = $("#project-change-form-toggle");
var projectNameChangeCancel = $("#project-name-change-cancel");
// this doesn't exist for command-line builds
var newBuildTargetInput = $("#build-input");
var newBuildTargetBuildBtn = $("#build-button");
var selectedTarget;
var updateProjectBtn = $("#update-project-button");
var cancelProjectBtn = $("#cancel-project-button");
/* Project name change functionality */
projectNameFormToggle.click(function(e){
e.preventDefault();
projectNameContainer.hide();
projectNameForm.fadeIn();
});
projectNameChangeCancel.click(function(e){
e.preventDefault();
projectNameForm.hide();
projectNameContainer.fadeIn();
$("#project-name-change-input").val(projectName.first().text());
});
$("#project-name-change-btn").click(function(){
var newProjectName = $("#project-name-change-input").val();
libtoaster.editCurrentProject({ projectName: newProjectName }, function (){
projectName.text(newProjectName);
libtoaster.ctx.projectName = newProjectName;
projectNameChangeCancel.click();
});
});
/* Nav bar activate state switcher */
$("#project-topbar .nav li a").each(function(){
if (window.location.pathname === $(this).attr('href'))
$(this).parent().addClass('active');
else
$(this).parent().removeClass('active');
});
if (!newBuildTargetInput.length) {
return;
}
/* the following only applies for non-command-line projects */
/* Recipe build input functionality */
if (ctx.numProjectLayers > 0 && ctx.machine){
newBuildTargetInput.removeAttr("disabled");
}
libtoaster.makeTypeahead(newBuildTargetInput,
libtoaster.ctx.recipesTypeAheadUrl, {}, function (item) {
selectedTarget = item;
newBuildTargetBuildBtn.removeAttr("disabled");
});
newBuildTargetInput.on('input', function () {
if ($(this).val().length === 0) {
newBuildTargetBuildBtn.attr("disabled", "disabled");
} else {
newBuildTargetBuildBtn.removeAttr("disabled");
}
});
newBuildTargetBuildBtn.click(function (e) {
e.preventDefault();
if (!newBuildTargetInput.val().trim()) {
return;
}
/* We use the value of the input field so as to maintain any command also
* added e.g. core-image-minimal:clean and because we can build targets
* that toaster doesn't yet know about
*/
selectedTarget = { name: newBuildTargetInput.val().trim() };
/* Fire off the build */
libtoaster.startABuild(null, selectedTarget.name,
function(){
window.location.replace(libtoaster.ctx.projectBuildsUrl);
}, null);
});
updateProjectBtn.click(function (e) {
e.preventDefault();
selectedTarget = { name: "_PROJECT_PREPARE_" };
/* Save current default build image, fire off the build */
libtoaster.updateProject(null, selectedTarget.name, newBuildTargetInput.val().trim(),
function(){
window.location.replace(libtoaster.ctx.projectSpecificPageUrl);
}, null);
});
cancelProjectBtn.click(function (e) {
e.preventDefault();
/* redirect to 'done/canceled' landing page */
window.location.replace(libtoaster.ctx.landingSpecificCancelURL);
});
/* Call makeProjectNameValidation function */
libtoaster.makeProjectNameValidation($("#project-name-change-input"),
$("#hint-error-project-name"), $("#validate-project-name"),
$("#project-name-change-btn"));
}

View File

@@ -0,0 +1,347 @@
(function(window){var QUnit,config,onErrorFnPrev,loggingCallbacks={},fileName=(sourceFromStacktrace(0)||"").replace(/(:\d+)+\)?/,"").replace(/.+\//,""),toString=Object.prototype.toString,hasOwn=Object.prototype.hasOwnProperty,Date=window.Date,now=Date.now||function(){return new Date().getTime();},globalStartCalled=false,runStarted=false,setTimeout=window.setTimeout,clearTimeout=window.clearTimeout,defined={document:window.document!==undefined,setTimeout:window.setTimeout!==undefined,sessionStorage:(function(){var x="qunit-test-string";try{sessionStorage.setItem(x,x);sessionStorage.removeItem(x);return true;}catch(e){return false;}}())},errorString=function(error){var name,message,errorString=error.toString();if(errorString.substring(0,7)==="[object"){name=error.name?error.name.toString():"Error";message=error.message?error.message.toString():"";if(name&&message){return name+": "+message;}else if(name){return name;}else if(message){return message;}else{return "Error";}}else{return errorString;}},objectValues =function(obj){var key,val,vals=QUnit.is("array",obj)?[]:{};for(key in obj){if(hasOwn.call(obj,key)){val=obj[key];vals[key]=val===Object(val)?objectValues(val):val;}}
return vals;};QUnit={};config={queue:[],blocking:true,reorder:true,altertitle:true,scrolltop:true,requireExpects:false,maxDepth:5,urlConfig:[{id:"hidepassed",label:"Hide passed tests",tooltip:"Only show tests and assertions that fail. Stored as query-strings."},{id:"noglobals",label:"Check for Globals",tooltip:"Enabling this will test if any test introduces new properties on the "+
"`window` object. Stored as query-strings."},{id:"notrycatch",label:"No try-catch",tooltip:"Enabling this will run tests outside of a try-catch block. Makes debugging "+
"exceptions in IE reasonable. Stored as query-strings."}],modules:[],currentModule:{name:"",tests:[]},callbacks:{}};config.modules.push(config.currentModule);(function(){var i,current,location=window.location||{search:"",protocol:"file:"},params=location.search.slice(1).split("&"),length=params.length,urlParams={};if(params[0]){for(i=0;i<length;i++){current=params[i].split("=");current[0]=decodeURIComponent(current[0]);current[1]=current[1]?decodeURIComponent(current[1]):true;if(urlParams[current[0]]){urlParams[current[0]]=[].concat(urlParams[current[0]],current[1]);}else{urlParams[current[0]]=current[1];}}}
if(urlParams.filter===true){delete urlParams.filter;}
QUnit.urlParams=urlParams;config.filter=urlParams.filter;if(urlParams.maxDepth){config.maxDepth=parseInt(urlParams.maxDepth,10)===-1?Number.POSITIVE_INFINITY:urlParams.maxDepth;}
config.testId=[];if(urlParams.testId){urlParams.testId=decodeURIComponent(urlParams.testId).split(",");for(i=0;i<urlParams.testId.length;i++){config.testId.push(urlParams.testId[i]);}}
QUnit.isLocal=location.protocol==="file:";QUnit.version="1.18.0";}());extend(QUnit,{module:function(name,testEnvironment){var currentModule={name:name,testEnvironment:testEnvironment,tests:[]};if(testEnvironment&&testEnvironment.setup){testEnvironment.beforeEach=testEnvironment.setup;delete testEnvironment.setup;}
if(testEnvironment&&testEnvironment.teardown){testEnvironment.afterEach=testEnvironment.teardown;delete testEnvironment.teardown;}
config.modules.push(currentModule);config.currentModule=currentModule;},asyncTest:function(testName,expected,callback){if(arguments.length===2){callback=expected;expected=null;}
QUnit.test(testName,expected,callback,true);},test:function(testName,expected,callback,async){var test;if(arguments.length===2){callback=expected;expected=null;}
test=new Test({testName:testName,expected:expected,async:async,callback:callback});test.queue();},skip:function(testName){var test=new Test({testName:testName,skip:true});test.queue();},//
start:function(count){var globalStartAlreadyCalled=globalStartCalled;if(!config.current){globalStartCalled=true;if(runStarted){throw new Error("Called start() outside of a test context while already started");}else if(globalStartAlreadyCalled||count>1){throw new Error("Called start() outside of a test context too many times");}else if(config.autostart){throw new Error("Called start() outside of a test context when "+
"QUnit.config.autostart was true");}else if(!config.pageLoaded){config.autostart=true;return;}}else{config.current.semaphore-=count||1;if(config.current.semaphore>0){return;}
if(config.current.semaphore<0){config.current.semaphore=0;QUnit.pushFailure("Called start() while already started (test's semaphore was 0 already)",sourceFromStacktrace(2));return;}}
resumeProcessing();},stop:function(count){if(!config.current){throw new Error("Called stop() outside of a test context");}
config.current.semaphore+=count||1;pauseProcessing();},config:config,is:function(type,obj){return QUnit.objectType(obj)===type;},objectType:function(obj){if(typeof obj==="undefined"){return "undefined";}//
if(obj===null){return "null";}
var match=toString.call(obj).match(/^\[object\s(.*)\]$/),type=match&&match[1]||"";switch(type){case "Number":if(isNaN(obj)){return "nan";}
return "number";case "String":case "Boolean":case "Array":case "Date":case "RegExp":case "Function":return type.toLowerCase();}
if(typeof obj==="object"){return "object";}
return undefined;},extend:extend,load:function(){config.pageLoaded=true;extend(config,{stats:{all:0,bad:0},moduleStats:{all:0,bad:0},started:0,updateRate:1000,autostart:true,filter:""},true);config.blocking=false;if(config.autostart){resumeProcessing();}}});(function(){var i,l,key,callbacks=["begin","done","log","testStart","testDone","moduleStart","moduleDone"];function registerLoggingCallback(key){var loggingCallback=function(callback){if(QUnit.objectType(callback)!=="function"){throw new Error("QUnit logging methods require a callback function as their first parameters.");}
config.callbacks[key].push(callback);};loggingCallbacks[key]=loggingCallback;return loggingCallback;}
for(i=0,l=callbacks.length;i<l;i++){key=callbacks[i];if(QUnit.objectType(config.callbacks[key])==="undefined"){config.callbacks[key]=[];}
QUnit[key]=registerLoggingCallback(key);}})();onErrorFnPrev=window.onerror;//
window.onerror=function(error,filePath,linerNr){var ret=false;if(onErrorFnPrev){ret=onErrorFnPrev(error,filePath,linerNr);}
if(ret!==true){if(QUnit.config.current){if(QUnit.config.current.ignoreGlobalErrors){return true;}
QUnit.pushFailure(error,filePath+":"+linerNr);}else{QUnit.test("global failure",extend(function(){QUnit.pushFailure(error,filePath+":"+linerNr);},{validTest:true}));}
return false;}
return ret;};function done(){var runtime,passed;config.autorun=true;if(config.previousModule){runLoggingCallbacks("moduleDone",{name:config.previousModule.name,tests:config.previousModule.tests,failed:config.moduleStats.bad,passed:config.moduleStats.all-config.moduleStats.bad,total:config.moduleStats.all,runtime:now()-config.moduleStats.started});}
delete config.previousModule;runtime=now()-config.started;passed=config.stats.all-config.stats.bad;runLoggingCallbacks("done",{failed:config.stats.bad,passed:passed,total:config.stats.all,runtime:runtime});}
function extractStacktrace(e,offset){offset=offset===undefined?4:offset;var stack,include,i;if(e.stack){stack=e.stack.split("\n");if(/^error$/i.test(stack[0])){stack.shift();}
if(fileName){include=[];for(i=offset;i<stack.length;i++){if(stack[i].indexOf(fileName)!==-1){break;}
include.push(stack[i]);}
if(include.length){return include.join("\n");}}
return stack[offset];//
}else if(e.sourceURL){if(/qunit.js$/.test(e.sourceURL)){return;}
return e.sourceURL+":"+e.line;}}
function sourceFromStacktrace(offset){var error=new Error();if(!error.stack){try{throw error;}catch(err){error=err;}}
return extractStacktrace(error,offset);}
function synchronize(callback,last){if(QUnit.objectType(callback)==="array"){while(callback.length){synchronize(callback.shift());}
return;}
config.queue.push(callback);if(config.autorun&&!config.blocking){process(last);}}
function process(last){function next(){process(last);}
var start=now();config.depth=(config.depth||0)+1;while(config.queue.length&&!config.blocking){if(!defined.setTimeout||config.updateRate<=0||((now()-start)<config.updateRate)){if(config.current){config.current.usedAsync=false;}
config.queue.shift()();}else{setTimeout(next,13);break;}}
config.depth--;if(last&&!config.blocking&&!config.queue.length&&config.depth===0){done();}}
function begin(){var i,l,modulesLog=[];if(!config.started){config.started=now();verifyLoggingCallbacks();if(config.modules[0].name===""&&config.modules[0].tests.length===0){config.modules.shift();}
for(i=0,l=config.modules.length;i<l;i++){modulesLog.push({name:config.modules[i].name,tests:config.modules[i].tests});}
runLoggingCallbacks("begin",{totalTests:Test.count,modules:modulesLog});}
config.blocking=false;process(true);}
function resumeProcessing(){runStarted=true;if(defined.setTimeout){setTimeout(function(){if(config.current&&config.current.semaphore>0){return;}
if(config.timeout){clearTimeout(config.timeout);}
begin();},13);}else{begin();}}
function pauseProcessing(){config.blocking=true;if(config.testTimeout&&defined.setTimeout){clearTimeout(config.timeout);config.timeout=setTimeout(function(){if(config.current){config.current.semaphore=0;QUnit.pushFailure("Test timed out",sourceFromStacktrace(2));}else{throw new Error("Test timed out");}
resumeProcessing();},config.testTimeout);}}
function saveGlobal(){config.pollution=[];if (config.noglobals){for(var key in window){if(hasOwn.call(window,key)){if(/^qunit-test-output/.test(key)){continue;}
config.pollution.push(key);}}}}
function checkPollution(){var newGlobals,deletedGlobals,old=config.pollution;saveGlobal();newGlobals=diff(config.pollution,old);if(newGlobals.length>0){QUnit.pushFailure("Introduced global variable(s): "+newGlobals.join(", "));}
deletedGlobals=diff(old,config.pollution);if(deletedGlobals.length>0){QUnit.pushFailure("Deleted global variable(s): "+deletedGlobals.join(", "));}}
function diff(a,b){var i,j,result=a.slice();for(i=0;i<result.length;i++){for(j=0;j<b.length;j++){if(result[i]===b[j]){result.splice(i,1);i--;break;}}}
return result;}
function extend(a,b,undefOnly){for(var prop in b){if(hasOwn.call(b,prop)){if(!(prop==="constructor"&&a===window)){if(b[prop]===undefined){delete a[prop];}else if(!(undefOnly&&typeof a[prop]!=="undefined")){a[prop]=b[prop];}}}}
return a;}
function runLoggingCallbacks(key,args){var i,l,callbacks;callbacks=config.callbacks[key];for(i=0,l=callbacks.length;i<l;i++){callbacks[i](args);}}
function verifyLoggingCallbacks(){var loggingCallback,userCallback;for(loggingCallback in loggingCallbacks){if(QUnit[loggingCallback]!==loggingCallbacks[loggingCallback]){userCallback=QUnit[loggingCallback];QUnit[loggingCallback]=loggingCallbacks[loggingCallback];QUnit[loggingCallback](userCallback);if(window.console&&window.console.warn){window.console.warn("QUnit."+loggingCallback+" was replaced with a new value.\n"+
"Please, check out the documentation on how to apply logging callbacks.\n"+
"Reference: http://api.qunitjs.com/category/callbacks/");}}}}
function inArray(elem,array){if(array.indexOf){return array.indexOf(elem);}
for(var i=0,length=array.length;i<length;i++){if(array[i]===elem){return i;}}
return-1;}
function Test(settings){var i,l;++Test.count;extend(this,settings);this.assertions=[];this.semaphore=0;this.usedAsync=false;this.module=config.currentModule;this.stack=sourceFromStacktrace(3);for(i=0,l=this.module.tests;i<l.length;i++){if(this.module.tests[i].name===this.testName){this.testName+=" ";}}
this.testId=generateHash(this.module.name,this.testName);this.module.tests.push({name:this.testName,testId:this.testId});if(settings.skip){this.callback=function(){};this.async=false;this.expected=0;}else{this.assert=new Assert(this);}}
Test.count=0;Test.prototype={before:function(){if(this.module!==config.previousModule||//
!hasOwn.call(config,"previousModule")){if(hasOwn.call(config,"previousModule")){runLoggingCallbacks("moduleDone",{name:config.previousModule.name,tests:config.previousModule.tests,failed:config.moduleStats.bad,passed:config.moduleStats.all-config.moduleStats.bad,total:config.moduleStats.all,runtime:now()-config.moduleStats.started});}
config.previousModule=this.module;config.moduleStats={all:0,bad:0,started:now()};runLoggingCallbacks("moduleStart",{name:this.module.name,tests:this.module.tests});}
config.current=this;this.testEnvironment=extend({},this.module.testEnvironment);delete this.testEnvironment.beforeEach;delete this.testEnvironment.afterEach;this.started=now();runLoggingCallbacks("testStart",{name:this.testName,module:this.module.name,testId:this.testId});if(!config.pollution){saveGlobal();}},run:function(){var promise;config.current=this;if(this.async){QUnit.stop();}
this.callbackStarted=now();if(config.notrycatch){promise=this.callback.call(this.testEnvironment,this.assert);this.resolvePromise(promise);return;}
try{promise=this.callback.call(this.testEnvironment,this.assert);this.resolvePromise(promise);}catch(e){this.pushFailure("Died on test #"+(this.assertions.length+1)+" "+
this.stack+": "+(e.message||e),extractStacktrace(e,0));saveGlobal();if(config.blocking){QUnit.start();}}},after:function(){checkPollution();},queueHook:function(hook,hookName){var promise,test=this;return function runHook(){config.current=test;if(config.notrycatch){promise=hook.call(test.testEnvironment,test.assert);test.resolvePromise(promise,hookName);return;}
try{promise=hook.call(test.testEnvironment,test.assert);test.resolvePromise(promise,hookName);}catch(error){test.pushFailure(hookName+" failed on "+test.testName+": "+(error.message||error),extractStacktrace(error,0));}};},hooks:function(handler){var hooks=[];if(this.skip){return hooks;}
if(this.module.testEnvironment&&QUnit.objectType(this.module.testEnvironment[handler])==="function"){hooks.push(this.queueHook(this.module.testEnvironment[handler],handler));}
return hooks;},finish:function(){config.current=this;if(config.requireExpects&&this.expected===null){this.pushFailure("Expected number of assertions to be defined, but expect() was "+
"not called.",this.stack);}else if(this.expected!==null&&this.expected!==this.assertions.length){this.pushFailure("Expected "+this.expected+" assertions, but "+
this.assertions.length+" were run",this.stack);}else if(this.expected===null&&!this.assertions.length){this.pushFailure("Expected at least one assertion, but none were run - call "+
"expect(0) to accept zero assertions.",this.stack);}
var i,bad=0;this.runtime=now()-this.started;config.stats.all+=this.assertions.length;config.moduleStats.all+=this.assertions.length;for(i=0;i<this.assertions.length;i++){if(!this.assertions[i].result){bad++;config.stats.bad++;config.moduleStats.bad++;}}
runLoggingCallbacks("testDone",{name:this.testName,module:this.module.name,skipped:!!this.skip,failed:bad,passed:this.assertions.length-bad,total:this.assertions.length,runtime:this.runtime,assertions:this.assertions,testId:this.testId,duration:this.runtime});QUnit.reset();config.current=undefined;},queue:function(){var bad,test=this;if(!this.valid()){return;}
function run(){synchronize([function(){test.before();},test.hooks("beforeEach"),function(){test.run();},test.hooks("afterEach").reverse(),function(){test.after();},function(){test.finish();}]);}
bad=QUnit.config.reorder&&defined.sessionStorage&&+sessionStorage.getItem("qunit-test-"+this.module.name+"-"+this.testName);if(bad){run();}else{synchronize(run,true);}},push:function(result,actual,expected,message){var source,details={module:this.module.name,name:this.testName,result:result,message:message,actual:actual,expected:expected,testId:this.testId,runtime:now()-this.started};if(!result){source=sourceFromStacktrace();if(source){details.source=source;}}
runLoggingCallbacks("log",details);this.assertions.push({result:!!result,message:message});},pushFailure:function(message,source,actual){if(!this instanceof Test){throw new Error("pushFailure() assertion outside test context, was "+
sourceFromStacktrace(2));}
var details={module:this.module.name,name:this.testName,result:false,message:message||"error",actual:actual||null,testId:this.testId,runtime:now()-this.started};if(source){details.source=source;}
runLoggingCallbacks("log",details);this.assertions.push({result:false,message:message});},resolvePromise:function(promise,phase){var then,message,test=this;if(promise!=null){then=promise.then;if(QUnit.objectType(then)==="function"){QUnit.stop();then.call(promise,QUnit.start,function(error){message="Promise rejected "+(!phase?"during":phase.replace(/Each$/,""))+
" "+test.testName+": "+(error.message||error);test.pushFailure(message,extractStacktrace(error,0));saveGlobal();QUnit.start();});}}},valid:function(){var include,filter=config.filter&&config.filter.toLowerCase(),module=QUnit.urlParams.module&&QUnit.urlParams.module.toLowerCase(),fullName=(this.module.name+": "+this.testName).toLowerCase();if(this.callback&&this.callback.validTest){return true;}
if(config.testId.length>0&&inArray(this.testId,config.testId)<0){return false;}
if(module&&(!this.module.name||this.module.name.toLowerCase()!==module)){return false;}
if(!filter){return true;}
include=filter.charAt(0)!=="!";if(!include){filter=filter.slice(1);}
if(fullName.indexOf(filter)!==-1){return include;}
return!include;}};QUnit.reset=function(){if(typeof window==="undefined"){return;}
var fixture=defined.document&&document.getElementById&&document.getElementById("qunit-fixture");if(fixture){fixture.innerHTML=config.fixture;}};QUnit.pushFailure=function(){if(!QUnit.config.current){throw new Error("pushFailure() assertion outside test context, in "+
sourceFromStacktrace(2));}
var currentTest=QUnit.config.current;return currentTest.pushFailure.apply(currentTest,arguments);};function generateHash(module,testName){var hex,i=0,hash=0,str=module+"\x1C"+testName,len=str.length;for(;i<len;i++){hash=((hash<<5)-hash)+str.charCodeAt(i);hash|=0;}
hex=(0x100000000+hash).toString(16);if(hex.length<8){hex="0000000"+hex;}
return hex.slice(-8);}
function Assert(testContext){this.test=testContext;}
QUnit.assert=Assert.prototype={expect:function(asserts){if(arguments.length===1){this.test.expected=asserts;}else{return this.test.expected;}},async:function(){var test=this.test,popped=false;test.semaphore+=1;test.usedAsync=true;pauseProcessing();return function done(){if(!popped){test.semaphore-=1;popped=true;resumeProcessing();}else{test.pushFailure("Called the callback returned from `assert.async` more than once",sourceFromStacktrace(2));}};},push:function(){var assert=this,currentTest=(assert instanceof Assert&&assert.test)||QUnit.config.current;if(!currentTest){throw new Error("assertion outside test context, in "+sourceFromStacktrace(2));}
if(currentTest.usedAsync===true&&currentTest.semaphore===0){currentTest.pushFailure("Assertion after the final `assert.async` was resolved",sourceFromStacktrace(2));}
if(!(assert instanceof Assert)){assert=currentTest.assert;}
return assert.test.push.apply(assert.test,arguments);},ok:function(result,message){message=message||(result?"okay":"failed, expected argument to be truthy, was: "+
QUnit.dump.parse(result));this.push(!!result,result,true,message);},notOk:function(result,message){message=message||(!result?"okay":"failed, expected argument to be falsy, was: "+
QUnit.dump.parse(result));this.push(!result,result,false,message);},equal:function(actual,expected,message){this.push(expected==actual,actual,expected,message);},notEqual:function(actual,expected,message){this.push(expected!=actual,actual,expected,message);},propEqual:function(actual,expected,message){actual=objectValues(actual);expected=objectValues(expected);this.push(QUnit.equiv(actual,expected),actual,expected,message);},notPropEqual:function(actual,expected,message){actual=objectValues(actual);expected=objectValues(expected);this.push(!QUnit.equiv(actual,expected),actual,expected,message);},deepEqual:function(actual,expected,message){this.push(QUnit.equiv(actual,expected),actual,expected,message);},notDeepEqual:function(actual,expected,message){this.push(!QUnit.equiv(actual,expected),actual,expected,message);},strictEqual:function(actual,expected,message){this.push(expected===actual,actual,expected,message);},notStrictEqual:function(actual,expected,message){this.push(expected!==actual,actual,expected,message);},"throws":function(block,expected,message){var actual,expectedType,expectedOutput=expected,ok=false,currentTest=(this instanceof Assert&&this.test)||QUnit.config.current;if(message==null&&typeof expected==="string"){message=expected;expected=null;}
currentTest.ignoreGlobalErrors=true;try{block.call(currentTest.testEnvironment);}catch(e){actual=e;}
currentTest.ignoreGlobalErrors=false;if(actual){expectedType=QUnit.objectType(expected);if(!expected){ok=true;expectedOutput=null;}else if(expectedType==="regexp"){ok=expected.test(errorString(actual));}else if(expectedType==="string"){ok=expected===errorString(actual);}else if(expectedType==="function"&&actual instanceof expected){ok=true;}else if(expectedType==="object"){ok=actual instanceof expected.constructor&&actual.name===expected.name&&actual.message===expected.message;}else if(expectedType==="function"&&expected.call({},actual)===true){expectedOutput= null;ok=true;}}
currentTest.assert.push(ok,actual,expectedOutput,message);}};(function(){Assert.prototype.raises=Assert.prototype["throws"];}());QUnit.equiv=(function(){function bindCallbacks(o,callbacks,args){var prop=QUnit.objectType(o);if(prop){if(QUnit.objectType(callbacks[prop])==="function"){return callbacks[prop].apply(callbacks,args);}else{return callbacks[prop];}}}
var innerEquiv,callers=[],parents=[],parentsB=[],getProto=Object.getPrototypeOf||function(obj){return obj.__proto__;},callbacks=(function(){function useStrictEquality(b,a){if(b instanceof a.constructor||a instanceof b.constructor){return a==b;}else{return a===b;}}
return{"string":useStrictEquality,"boolean":useStrictEquality,"number":useStrictEquality,"null":useStrictEquality,"undefined":useStrictEquality,"nan":function(b){return isNaN(b);},"date":function(b,a){return QUnit.objectType(b)==="date"&&a.valueOf()===b.valueOf();},"regexp":function(b,a){return QUnit.objectType(b)==="regexp"&&a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline&&a.sticky===b.sticky;},"function":function(){var caller=callers[callers.length-1];return caller!==Object&&typeof caller!=="undefined";},"array":function(b,a){var i,j,len,loop,aCircular,bCircular;if(QUnit.objectType(b)!=="array"){return false;}
len=a.length;if(len!==b.length){return false;}
parents.push(a);parentsB.push(b);for(i=0;i<len;i++){loop=false;for(j=0;j<parents.length;j++){aCircular=parents[j]===a[i];bCircular=parentsB[j]===b[i];if(aCircular||bCircular){ if(a[i]===b[i]||aCircular&&bCircular){loop=true;}else{parents.pop();parentsB.pop();return false;}}}
if(!loop&&!innerEquiv(a[i],b[i])){parents.pop();parentsB.pop();return false;}}
parents.pop();parentsB.pop();return true;},"object":function(b,a){var i,j,loop,aCircular,bCircular,eq=true,aProperties=[],bProperties=[];if(a.constructor!==b.constructor){if(!((getProto(a)===null&&getProto(b)===Object.prototype)||(getProto(b)===null&&getProto(a)===Object.prototype))){return false;}}
callers.push(a.constructor);parents.push(a);parentsB.push(b);for(i in a){loop=false;for(j=0;j<parents.length;j++){aCircular=parents[j]===a[i];bCircular=parentsB[j]===b[i];if(aCircular||bCircular){if(a[i]===b[i]||aCircular&&bCircular){loop=true;}else{eq=false;break;}}}
aProperties.push(i);if(!loop&&!innerEquiv(a[i],b[i])){eq=false;break;}}
parents.pop();parentsB.pop();callers.pop();for(i in b){bProperties.push(i);}
return eq&&innerEquiv(aProperties.sort(),bProperties.sort());}};}());innerEquiv=function(){var args=[].slice.apply(arguments);if(args.length<2){return true;}
return((function(a,b){if(a===b){return true;}else if(a===null||b===null||typeof a==="undefined"||typeof b==="undefined"||QUnit.objectType(a)!==QUnit.objectType(b)){return false;}else{return bindCallbacks(a,callbacks,[b,a]);}}(args[0],args[1]))&&innerEquiv.apply(this,args.splice(1,args.length-1)));};return innerEquiv;}());QUnit.dump=(function(){function quote(str){return "\""+str.toString().replace(/"/g,"\\\"")+"\"";}
function literal(o){return o+"";}
function join(pre,arr,post){var s=dump.separator(),base=dump.indent(),inner=dump.indent(1);if(arr.join){arr=arr.join(","+s+inner);}
if(!arr){return pre+post;}
return[pre,inner+arr,base+post].join(s);}
function array(arr,stack){var i=arr.length,ret=new Array(i);if(dump.maxDepth&&dump.depth>dump.maxDepth){return "[object Array]";}
this.up();while(i--){ret[i]=this.parse(arr[i],undefined,stack);}
this.down();return join("[",ret,"]");}
var reName=/^function (\w+)/,dump={parse:function(obj,objType,stack){stack=stack||[];var res,parser,parserType,inStack=inArray(obj,stack);if(inStack!==-1){return "recursion("+(inStack-stack.length)+")";}
objType=objType||this.typeOf(obj);parser=this.parsers[objType];parserType=typeof parser;if(parserType==="function"){stack.push(obj);res=parser.call(this,obj,stack);stack.pop();return res;}
return(parserType==="string")?parser:this.parsers.error;},typeOf:function(obj){var type;if(obj===null){type="null";}else if(typeof obj==="undefined"){type="undefined";}else if(QUnit.is("regexp",obj)){type="regexp";}else if(QUnit.is("date",obj)){type="date";}else if(QUnit.is("function",obj)){type="function";}else if(obj.setInterval!==undefined&&obj.document!==undefined&&obj.nodeType===undefined){type="window";}else if(obj.nodeType===9){type="document";}else if(obj.nodeType){type="node";}else if(toString.call(obj)==="[object Array]"||(typeof obj.length==="number"&&obj.item!==undefined&&(obj.length?obj.item(0)===obj[ 0]:(obj.item(0)===null&&obj[0]===undefined)))){type="array";}else if(obj.constructor===Error.prototype.constructor){type="error";}else{type=typeof obj;}
return type;},separator:function(){return this.multiline?this.HTML?"<br />":"\n":this.HTML?"&#160;":" ";},indent:function(extra){if(!this.multiline){return "";}
var chr=this.indentChar;if(this.HTML){chr=chr.replace(/\t/g," ").replace(/ /g,"&#160;");}
return new Array(this.depth+(extra||0)).join(chr);},up:function(a){this.depth+=a||1;},down:function(a){this.depth-=a||1;},setParser:function(name,parser){this.parsers[name]=parser;},quote:quote,literal:literal,join:join,depth:1,maxDepth:QUnit.config.maxDepth,parsers:{window:"[Window]",document:"[Document]",error:function(error){return "Error(\""+error.message+"\")";},unknown:"[Unknown]","null":"null","undefined":"undefined","function":function(fn){var ret="function",name="name" in fn?fn.name:(reName.exec(fn)||[])[1];if(name){ret+=" "+name;}
ret+="( ";ret=[ret,dump.parse(fn,"functionArgs"),"){"].join("");return join(ret,dump.parse(fn,"functionCode"),"}");},array:array,nodelist:array,"arguments":array,object:function(map,stack){var keys,key,val,i,nonEnumerableProperties,ret=[];if(dump.maxDepth&&dump.depth>dump.maxDepth){return "[object Object]";}
dump.up(); keys=[];for(key in map){keys.push(key);}
nonEnumerableProperties=["message","name"];for(i in nonEnumerableProperties){key=nonEnumerableProperties[i];if(key in map&&inArray(key,keys)<0){keys.push(key);}}
keys.sort();for(i=0;i<keys.length;i++){key=keys[i];val=map[key];ret.push(dump.parse(key,"key")+": "+
dump.parse(val,undefined,stack));}
dump.down();return join("{",ret,"}");},node:function(node){var len,i,val,open=dump.HTML?"&lt;":"<",close=dump.HTML?"&gt;":">",tag=node.nodeName.toLowerCase(),ret=open+tag,attrs=node.attributes;if(attrs){for(i=0,len=attrs.length;i<len;i++){val=attrs[i].nodeValue;if(val&&val!=="inherit"){ret+=" "+attrs[i].nodeName+"="+
dump.parse(val,"attribute");}}}
ret+=close;if(node.nodeType===3||node.nodeType===4){ret+=node.nodeValue;}
return ret+open+"/"+tag+close;},functionArgs:function(fn){var args,l=fn.length;if(!l){return "";}
args=new Array(l);while(l--){args[l]=String.fromCharCode(97+l);}
return " "+args.join(", ")+" ";},key:quote,functionCode:"[code]",attribute:quote,string:quote,date:quote,regexp:literal,number:literal,"boolean":literal},HTML:false,indentChar:" ",multiline:true};return dump;}());QUnit.jsDump=QUnit.dump;if(typeof window!=="undefined"){(function(){var i,assertions=Assert.prototype;function applyCurrent(current){return function(){var assert=new Assert(QUnit.config.current);current.apply(assert,arguments);};}
for(i in assertions){QUnit[i]=applyCurrent(assertions[i]);}})();(function(){var i,l,keys=["test","module","expect","asyncTest","start","stop","ok","notOk","equal","notEqual","propEqual","notPropEqual","deepEqual","notDeepEqual","strictEqual","notStrictEqual","throws"];for(i=0,l=keys.length;i<l;i++){window[keys[i]]=QUnit[keys[i]];}})();window.QUnit=QUnit;}
if(typeof module!=="undefined"&&module&&module.exports){module.exports=QUnit;module.exports.QUnit=QUnit;}
if(typeof exports!=="undefined"&&exports){exports.QUnit=QUnit;}
if(typeof define==="function"&&define.amd){define(function(){return QUnit;});QUnit.config.autostart=false;}}((function(){return this;})()));//
/**/
QUnit.diff=(function(){function DiffMatchPatch(){this.DiffTimeout=1.0;this.DiffEditCost=4;}
var DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;DiffMatchPatch.prototype.DiffMain=function(text1,text2,optChecklines,optDeadline){var deadline,checklines,commonlength,commonprefix,commonsuffix,diffs;if(typeof optDeadline==="undefined"){if(this.DiffTimeout<=0){optDeadline=Number.MAX_VALUE;}else{ optDeadline=(new Date()).getTime()+this.DiffTimeout*1000;}}
deadline=optDeadline;if(text1===null||text2===null){throw new Error("Null input. (DiffMain)");}
if(text1===text2){if(text1){return[[DIFF_EQUAL,text1]];}
return[];}
if(typeof optChecklines==="undefined"){optChecklines=true;}
checklines=optChecklines;commonlength=this.diffCommonPrefix(text1,text2);commonprefix=text1.substring(0,commonlength);text1=text1.substring(commonlength);text2=text2.substring(commonlength);commonlength=this.diffCommonSuffix(text1,text2);commonsuffix=text1.substring(text1.length-commonlength);text1=text1.substring(0,text1.length-commonlength);text2=text2.substring(0,text2.length-commonlength);diffs=this.diffCompute(text1,text2,checklines,deadline);if(commonprefix){diffs.unshift([DIFF_EQUAL,commonprefix]);}
if(commonsuffix){diffs.push([DIFF_EQUAL,commonsuffix]);}
this.diffCleanupMerge(diffs);return diffs;};DiffMatchPatch.prototype.diffCleanupEfficiency=function(diffs){var changes,equalities,equalitiesLength,lastequality,pointer,preIns,preDel,postIns,postDel;changes=false;equalities=[];equalitiesLength=0;lastequality=null;pointer=0;preIns=false;preDel=false;postIns=false;postDel=false;while(pointer<diffs.length){if(diffs[pointer][0]===DIFF_EQUAL){if(diffs[pointer][1].length<this.DiffEditCost&&(postIns||postDel)){equalities[equalitiesLength++]=pointer;preIns=postIns;preDel=postDel;lastequality=diffs[pointer][1];}else{equalitiesLength=0;lastequality=null;}
postIns=postDel=false;}else{if(diffs[pointer][0]===DIFF_DELETE){postDel=true;}else{postIns=true;}
if(lastequality&&((preIns&&preDel&&postIns&&postDel)||((lastequality.length<this.DiffEditCost/2)&&(preIns+preDel+postIns+postDel)===3))){diffs.splice(equalities[equalitiesLength-1],0,[DIFF_DELETE,lastequality]);diffs[equalities[equalitiesLength-1]+1][0]=DIFF_INSERT;equalitiesLength--;lastequality=null;if(preIns&&preDel){postIns=postDel=true;equalitiesLength=0;}else{equalitiesLength--;pointer=equalitiesLength>0?equalities[equalitiesLength-1]:-1;postIns=postDel=false;}
changes=true;}}
pointer++;}
if(changes){this.diffCleanupMerge(diffs);}};DiffMatchPatch.prototype.diffPrettyHtml=function(diffs){var op,data,x,html=[];for(x=0;x<diffs.length;x++){op=diffs[x][0];data=diffs[x][1];switch(op){case DIFF_INSERT:html[x]="<ins>"+data+"</ins>";break;case DIFF_DELETE:html[x]="<del>"+data+"</del>";break;case DIFF_EQUAL:html[x]="<span>"+data+"</span>";break;}}
return html.join("");};DiffMatchPatch.prototype.diffCommonPrefix=function(text1,text2){var pointermid,pointermax,pointermin,pointerstart;if(!text1||!text2||text1.charAt(0)!==text2.charAt(0)){return 0;}
pointermin=0;pointermax=Math.min(text1.length,text2.length);pointermid=pointermax;pointerstart=0;while(pointermin<pointermid){if(text1.substring(pointerstart,pointermid)===text2.substring(pointerstart,pointermid)){pointermin=pointermid;pointerstart=pointermin;}else{pointermax=pointermid;}
pointermid=Math.floor((pointermax-pointermin)/2+pointermin);}
return pointermid;};DiffMatchPatch.prototype.diffCommonSuffix=function(text1,text2){var pointermid,pointermax,pointermin,pointerend;if(!text1||!text2||text1.charAt(text1.length-1)!==text2.charAt(text2.length-1)){return 0;}
pointermin=0;pointermax=Math.min(text1.length,text2.length);pointermid=pointermax;pointerend=0;while(pointermin<pointermid){if(text1.substring(text1.length-pointermid,text1.length-pointerend)===text2.substring(text2.length-pointermid,text2.length-pointerend)){pointermin=pointermid;pointerend=pointermin;}else{pointermax=pointermid;}
pointermid=Math.floor((pointermax-pointermin)/2+pointermin);}
return pointermid;};/**/
DiffMatchPatch.prototype.diffCompute=function(text1,text2,checklines,deadline){var diffs,longtext,shorttext,i,hm,text1A,text2A,text1B,text2B,midCommon,diffsA,diffsB;if(!text1){return[[DIFF_INSERT,text2]];}
if(!text2){return[[DIFF_DELETE,text1]];}
longtext=text1.length>text2.length?text1:text2;shorttext=text1.length>text2.length?text2:text1;i=longtext.indexOf(shorttext);if(i!==-1){diffs=[[DIFF_INSERT,longtext.substring(0,i)],[DIFF_EQUAL,shorttext],[DIFF_INSERT,longtext.substring(i+shorttext.length)]];if(text1.length>text2.length){diffs[0][0]=diffs[2][0]=DIFF_DELETE;}
return diffs;}
if(shorttext.length===1){return[[DIFF_DELETE,text1],[DIFF_INSERT,text2]];}
hm =this.diffHalfMatch(text1,text2);if(hm){text1A=hm[0];text1B=hm[1];text2A=hm[2];text2B=hm[3];midCommon=hm[4];diffsA=this.DiffMain(text1A,text2A,checklines,deadline);diffsB=this.DiffMain(text1B,text2B,checklines,deadline);return diffsA.concat([[DIFF_EQUAL,midCommon]],diffsB);}
if(checklines&&text1.length>100&&text2.length>100){return this.diffLineMode(text1,text2,deadline);}
return this.diffBisect(text1,text2,deadline);};DiffMatchPatch.prototype.diffHalfMatch=function(text1,text2){var longtext,shorttext,dmp,text1A,text2B,text2A,text1B,midCommon,hm1,hm2,hm;if(this.DiffTimeout<=0){return null;}
longtext=text1.length>text2.length?text1:text2;shorttext=text1.length>text2.length?text2:text1;if(longtext.length<4||shorttext.length*2<longtext.length){return null;}
dmp=this;/**/
function diffHalfMatchI(longtext,shorttext,i){var seed,j,bestCommon,prefixLength,suffixLength,bestLongtextA,bestLongtextB,bestShorttextA,bestShorttextB;seed=longtext.substring(i,i+Math.floor(longtext.length/4));j=-1;bestCommon="";while((j=shorttext.indexOf(seed,j+1))!==-1){prefixLength=dmp.diffCommonPrefix(longtext.substring(i),shorttext.substring(j));suffixLength=dmp.diffCommonSuffix(longtext.substring(0,i),shorttext.substring(0,j));if(bestCommon.length<suffixLength+prefixLength){bestCommon=shorttext.substring(j-suffixLength,j)+
shorttext.substring(j,j+prefixLength);bestLongtextA=longtext.substring(0,i-suffixLength);bestLongtextB=longtext.substring(i+prefixLength);bestShorttextA=shorttext.substring(0,j-suffixLength);bestShorttextB=shorttext.substring(j+prefixLength);}}
if(bestCommon.length*2>=longtext.length){return[bestLongtextA,bestLongtextB,bestShorttextA,bestShorttextB,bestCommon
];}else{return null;}}
hm1=diffHalfMatchI(longtext,shorttext,Math.ceil(longtext.length/4));hm2=diffHalfMatchI(longtext,shorttext,Math.ceil(longtext.length/2));if(!hm1&&!hm2){return null;}else if(!hm2){hm=hm1;}else if(!hm1){hm=hm2;}else{hm=hm1[4].length>hm2[4].length?hm1:hm2;}
text1A,text1B,text2A,text2B;if(text1.length>text2.length){text1A=hm[0];text1B=hm[1];text2A=hm[2];text2B=hm[3];}else{text2A=hm[0];text2B=hm[1];text1A=hm[2];text1B=hm[3];}
midCommon=hm[4];return[text1A,text1B,text2A,text2B,midCommon];};DiffMatchPatch.prototype.diffLineMode=function(text1,text2,deadline){var a,diffs,linearray,pointer,countInsert,countDelete,textInsert,textDelete,j;a=this.diffLinesToChars(text1,text2);text1=a.chars1;text2=a.chars2;linearray=a.lineArray;diffs=this.DiffMain(text1,text2,false,deadline);
this.diffCharsToLines(diffs,linearray);this.diffCleanupSemantic(diffs);diffs.push([DIFF_EQUAL,""]);pointer=0;countDelete=0;countInsert=0;textDelete="";textInsert="";while(pointer<diffs.length){switch(diffs[pointer][0]){case DIFF_INSERT:countInsert++;textInsert+=diffs[pointer][1];break;case DIFF_DELETE:countDelete++;textDelete+=diffs[pointer][1];break;case DIFF_EQUAL:if(countDelete>=1&&countInsert>=1){diffs.splice(pointer-countDelete-countInsert,countDelete+countInsert);pointer=pointer-countDelete-countInsert;a=this.DiffMain(textDelete,textInsert,false,deadline);for(j=a.length-1;j>=0;j--){diffs.splice(pointer,0,a[j]);}
pointer=pointer+a.length;}
countInsert=0;countDelete=0;textDelete="";textInsert="";break;}
pointer++;}
diffs.pop();return diffs;};DiffMatchPatch.prototype.diffBisect=function(text1,text2,deadline){var text1Length,text2Length,maxD,vOffset,vLength,v1,v2,x,delta,front,k1start,k1end,k2start,k2end,k2Offset,k1Offset,x1,x2,y1,y2,d,k1,k2;text1Length=text1.length;text2Length=text2.length;maxD=Math.ceil((text1Length+text2Length)/2);vOffset=maxD;vLength=2*maxD;v1=new Array(vLength);v2=new Array(vLength);for(x=0;x<vLength;x++){v1[x]=-1;v2[x]=-1;}
v1[vOffset+1]=0;v2[vOffset+1]=0;delta=text1Length-text2Length;front=(delta%2!==0);k1start=0;k1end=0;k2start=0;k2end=0;for(d=0;d<maxD;d++){if((new Date()).getTime()>deadline){break;}
for(k1=-d+k1start;k1<=d-k1end;k1+=2){k1Offset=vOffset+k1;if(k1===-d||(k1!==d&&v1[k1Offset-1]<v1[k1Offset+1])){x1=v1[k1Offset+1];}else{x1=v1[k1Offset-1]+1;}
y1=x1-k1;while(x1<text1Length&&y1<text2Length&&text1.charAt(x1)===text2.charAt(y1)){x1++;y1++;}
v1[k1Offset]=x1;if(x1>text1Length){k1end+=2;}else if(y1>text2Length){k1start+=2;}else if(front){k2Offset=vOffset+delta-k1;if(k2Offset>=0&&k2Offset<vLength&&v2[k2Offset]!==-1){x2=text1Length-v2[k2Offset];if(x1>=x2){return this.diffBisectSplit(text1,text2,x1,y1,deadline);}}
}}
for(k2=-d+k2start;k2<=d-k2end;k2+=2){k2Offset=vOffset+k2;if(k2===-d||(k2!==d&&v2[k2Offset-1]<v2[k2Offset+1])){x2=v2[k2Offset+1];}else{x2=v2[k2Offset-1]+1;}
y2=x2-k2;while(x2<text1Length&&y2<text2Length&&text1.charAt(text1Length-x2-1)===text2.charAt(text2Length-y2-1)){x2++;y2++;}
v2[k2Offset]=x2;if(x2>text1Length){k2end+=2;}else if(y2>text2Length){k2start+=2;}else if(!front){k1Offset=vOffset+delta-k2;if(k1Offset>=0&&k1Offset<vLength&&v1[k1Offset]!==-1){x1=v1[k1Offset];y1=vOffset+x1-k1Offset;x2=text1Length-x2;if(x1>=x2){return this.diffBisectSplit(text1,text2,x1,y1,deadline);}}}}}
return[[DIFF_DELETE,text1],[DIFF_INSERT,text2]];};/**/
DiffMatchPatch.prototype.diffBisectSplit=function(text1,text2,x,y,deadline){var text1a,text1b,text2a,text2b,diffs,diffsb;text1a=text1.substring(0,x);text2a=text2.substring(0,y);text1b=text1.substring(x);text2b=text2.substring(y);diffs=this.DiffMain(text1a,text2a,false,deadline);diffsb=this.DiffMain(text1b,text2b,false,deadline);return diffs.concat(diffsb);};DiffMatchPatch.prototype.diffCleanupSemantic=function(diffs){var changes,equalities,equalitiesLength,lastequality,pointer,lengthInsertions2,lengthDeletions2,lengthInsertions1,lengthDeletions1,deletion,insertion,overlapLength1,overlapLength2;changes=false;equalities=[];equalitiesLength=0;lastequality=null;pointer=0;lengthInsertions1=0;lengthDeletions1=0;lengthInsertions2=0;lengthDeletions2=0;while(pointer<diffs.length){if(diffs[pointer][0]===DIFF_EQUAL){equalities[equalitiesLength++]=pointer;lengthInsertions1=lengthInsertions2;lengthDeletions1=lengthDeletions2;lengthInsertions2=0;lengthDeletions2=0;lastequality=diffs[pointer][1];}else{if(diffs[pointer][0]===DIFF_INSERT){lengthInsertions2+=diffs[pointer][1].length;}else{lengthDeletions2+=diffs[pointer][1].length;}
if(lastequality&&(lastequality.length<=Math.max(lengthInsertions1,lengthDeletions1))&&(lastequality.length<=Math.max(lengthInsertions2,lengthDeletions2))){diffs.splice(equalities[equalitiesLength-1],0,[DIFF_DELETE,lastequality]);diffs[equalities[equalitiesLength-1]+1][0]=DIFF_INSERT;equalitiesLength--;equalitiesLength--;pointer=equalitiesLength>0?equalities[equalitiesLength-1]:-1;lengthInsertions1=0;lengthDeletions1=0;lengthInsertions2=0;lengthDeletions2=0;lastequality=null;changes=true;}}
pointer++;}
if(changes){this.diffCleanupMerge(diffs);}
pointer=1;while(pointer<diffs.length){if(diffs[pointer-1][0]===DIFF_DELETE&&diffs[pointer][0]===DIFF_INSERT){deletion=diffs[pointer-1][1];insertion=diffs[pointer][1];overlapLength1=this.diffCommonOverlap(deletion,insertion);overlapLength2=this.diffCommonOverlap(insertion,deletion);if(overlapLength1>=overlapLength2){if(overlapLength1>=deletion.length/2||overlapLength1>=insertion.length/2){diffs.splice(pointer,0,[DIFF_EQUAL,insertion.substring(0,overlapLength1)]);diffs[pointer-1][1]=deletion.substring(0,deletion.length-overlapLength1);diffs[pointer+1][1]=insertion.substring(overlapLength1);pointer++;}}else{if(overlapLength2>=deletion.length/2||overlapLength2>=insertion.length/2){diffs.splice(pointer,0,[DIFF_EQUAL,deletion.substring(0,overlapLength2)]);diffs[pointer-1][0]=DIFF_INSERT;diffs[pointer-1][1]=insertion.substring(0,insertion.length-overlapLength2);diffs[pointer+1][0]=DIFF_DELETE;diffs[pointer+1][1]=deletion.substring(overlapLength2);pointer++;}}
pointer++;}
pointer++;}};DiffMatchPatch.prototype.diffCommonOverlap=function(text1,text2){var text1Length,text2Length,textLength,best,length,pattern,found;text1Length=text1.length;text2Length=text2.length;if(text1Length===0||text2Length===0){return 0;}
if(text1Length>text2Length){text1=text1.substring(text1Length-text2Length);}else if(text1Length<text2Length){ text2=text2.substring(0,text1Length);}
textLength=Math.min(text1Length,text2Length);if(text1===text2){return textLength;}
best=0;length=1;while(true){pattern=text1.substring(textLength-length);found=text2.indexOf(pattern);if(found===-1){return best;}
length+=found;if(found===0||text1.substring(textLength-length)===text2.substring(0,length)){best=length;length++;}}};/**/
DiffMatchPatch.prototype.diffLinesToChars=function(text1,text2){var lineArray,lineHash,chars1,chars2;lineArray=[];lineHash={};lineArray[0]="";function diffLinesToCharsMunge(text){var chars,lineStart,lineEnd,lineArrayLength,line;chars="";lineStart=0;lineEnd=-1;lineArrayLength=lineArray.length;while(lineEnd<text.length-1){lineEnd=text.indexOf("\n",lineStart);if(lineEnd===-1){lineEnd=text.length-1;}
line=text.substring(lineStart,lineEnd+1);lineStart=lineEnd+1;if(lineHash.hasOwnProperty?lineHash.hasOwnProperty(line):(lineHash[line]!==undefined)){chars+=String.fromCharCode(lineHash[line]);}else{chars+=String.fromCharCode(lineArrayLength);lineHash[line]=lineArrayLength;lineArray[lineArrayLength++]=line;}}
return chars;}
chars1=diffLinesToCharsMunge(text1);chars2=diffLinesToCharsMunge(text2);return{chars1:chars1,chars2:chars2,lineArray:lineArray};};DiffMatchPatch.prototype.diffCharsToLines=function(diffs,lineArray){var x,chars,text,y;for(x=0;x<diffs.length;x++){chars=diffs[x][1];text=[];for(y=0;y<chars.length;y++){text[y]=lineArray[chars.charCodeAt(y)];}
diffs[x][1]=text.join("");}};DiffMatchPatch.prototype.diffCleanupMerge=function(diffs){var pointer,countDelete,countInsert,textInsert,textDelete,commonlength,changes;diffs.push([DIFF_EQUAL,""]);pointer=0;countDelete=0;countInsert=0;textDelete="";textInsert="";commonlength;while(pointer<diffs.length){switch(diffs[pointer][0]){case DIFF_INSERT:countInsert++;textInsert+=diffs[pointer][1];pointer++;break;case DIFF_DELETE:countDelete++;textDelete+=diffs[pointer][1];pointer++;break;case DIFF_EQUAL:if(countDelete+countInsert>1){if(countDelete!==0&&countInsert!==0){commonlength=this.diffCommonPrefix(textInsert,textDelete);if(commonlength!==0){if((pointer-countDelete-countInsert)>0&&diffs[pointer-countDelete-countInsert-1][0]===
DIFF_EQUAL){diffs[pointer-countDelete-countInsert-1][1]+=textInsert.substring(0,commonlength);}else{diffs.splice(0,0,[DIFF_EQUAL,textInsert.substring(0,commonlength)]);pointer++;}
textInsert=textInsert.substring(commonlength);textDelete=textDelete.substring(commonlength);}
commonlength=this.diffCommonSuffix(textInsert,textDelete);if(commonlength!==0){diffs[pointer][1]=textInsert.substring(textInsert.length-
commonlength)+diffs[pointer][1];textInsert=textInsert.substring(0,textInsert.length-
commonlength);textDelete=textDelete.substring(0,textDelete.length-
commonlength);}}
if(countDelete===0){diffs.splice(pointer-countInsert,countDelete+countInsert,[DIFF_INSERT,textInsert]);}else if(countInsert===0){diffs.splice(pointer-countDelete,countDelete+countInsert,[DIFF_DELETE,textDelete]);}else{diffs.splice(pointer-countDelete-countInsert, countDelete+countInsert,[DIFF_DELETE,textDelete],[DIFF_INSERT,textInsert]);}
pointer=pointer-countDelete-countInsert+(countDelete?1:0)+(countInsert?1:0)+1;}else if(pointer!==0&&diffs[pointer-1][0]===DIFF_EQUAL){diffs[pointer-1][1]+=diffs[pointer][1];diffs.splice(pointer,1);}else{pointer++;}
countInsert=0;countDelete=0;textDelete="";textInsert="";break;}}
if(diffs[diffs.length-1][1]===""){diffs.pop();}
changes=false;pointer=1;while(pointer<diffs.length-1){if(diffs[pointer-1][0]===DIFF_EQUAL&&diffs[pointer+1][0]===DIFF_EQUAL){if(diffs[pointer][1].substring(diffs[pointer][1].length-
diffs[pointer-1][1].length)===diffs[pointer-1][1]){diffs[pointer][1]=diffs[pointer-1][1]+
diffs[pointer][1].substring(0,diffs[pointer][1].length-
diffs[pointer-1][1].length);diffs[pointer+1][1]=diffs[pointer-1][1]+diffs[pointer+1][1];diffs.splice(pointer-1,1);changes=true;}else if(diffs[pointer][1].substring(0,diffs[pointer+1][1].length)===diffs[pointer+1][1]){diffs[pointer-1][1]+=diffs[pointer+1][1];diffs[pointer][1]=diffs[pointer][1].substring(diffs[pointer+1][1].length)+
diffs[pointer+1][1];diffs.splice(pointer+1,1);changes=true;}}
pointer++;}
if(changes){this.diffCleanupMerge(diffs);}};return function(o,n){var diff,output,text;diff=new DiffMatchPatch();output=diff.DiffMain(o,n);diff.diffCleanupEfficiency(output);text=diff.diffPrettyHtml(output);return text;};}());//
(function(){QUnit.init=function(){var tests,banner,result,qunit,config=QUnit.config;config.stats={all:0,bad:0};config.moduleStats={all:0,bad:0};config.started=0;config.updateRate=1000;config.blocking=false;config.autostart=true;config.autorun=false;config.filter="";config.queue=[];if(typeof window==="undefined"){return;}
qunit=id("qunit");if(qunit){qunit.innerHTML="<h1 id='qunit-header'>"+escapeText(document.title)+"</h1>"+
"<h2 id='qunit-banner'></h2>"+
"<div id='qunit-testrunner-toolbar'></div>"+
"<h2 id='qunit-userAgent'></h2>"+
"<ol id='qunit-tests'></ol>";}
tests=id("qunit-tests");banner=id("qunit-banner");result=id("qunit-testresult");if(tests){tests.innerHTML="";}
if(banner){banner.className="";}
if(result){result.parentNode.removeChild(result);}
if(tests){result=document.createElement("p");result.id="qunit-testresult";result.className="result";tests.parentNode.insertBefore(result,tests);result.innerHTML="Running...<br />&#160;";}};if(typeof window==="undefined"){return;}
var config=QUnit.config,hasOwn=Object.prototype.hasOwnProperty,defined={document:window.document!==undefined,sessionStorage:(function(){var x="qunit-test-string";try{sessionStorage.setItem(x,x);sessionStorage.removeItem(x);return true;}catch(e){return false;}}())},modulesList=[];function escapeText(s){if(!s){return "";}
s=s+"";return s.replace(/['"<>&]/g,function(s){switch(s){case "'":return "&#039;";case "\"":return "&quot;";case "<":return "&lt;";case ">":return "&gt;";case "&":return "&amp;";}});}
function addEvent(elem,type,fn){if(elem.addEventListener){elem.addEventListener(type,fn,false);}else if(elem.attachEvent){elem.attachEvent("on"+type,function(){var event=window.event;if(!event.target){event.target=event.srcElement||document;}
fn.call(elem,event);});}}
function addEvents(elems,type,fn){var i=elems.length;while(i--){addEvent(elems[i],type,fn);}}
function hasClass(elem,name){return(" "+elem.className+" ").indexOf(" "+name+" ")>=0;}
function addClass(elem,name){if(!hasClass(elem,name)){elem.className+=(elem.className?" ":"")+name;}}
function toggleClass(elem,name){if(hasClass(elem,name)){removeClass(elem,name);}else{addClass(elem,name);}}
function removeClass(elem,name){var set=" "+elem.className+" ";while(set.indexOf(" "+name+" ")>=0){set=set.replace(" "+name+" "," ");}
elem.className=typeof set.trim==="function"?set.trim():set.replace(/^\s+|\s+$/g,"");}
function id(name){return defined.document&&document.getElementById&&document.getElementById(name);}
function getUrlConfigHtml(){var i,j,val,escaped,escapedTooltip,selection=false,len=config.urlConfig.length,urlConfigHtml="";for(i=0;i<len;i++){val=config.urlConfig[i];if(typeof val==="string"){val={id:val,label:val};}
escaped=escapeText(val.id);escapedTooltip=escapeText(val.tooltip);if(config[val.id]===undefined){config[val.id]=QUnit.urlParams[val.id];}
if(!val.value||typeof val.value==="string"){urlConfigHtml+="<input id='qunit-urlconfig-"+escaped+
"' name='"+escaped+"' type='checkbox'"+(val.value?" value='"+escapeText(val.value)+"'":"")+(config[val.id]?" checked='checked'":"")+
" title='"+escapedTooltip+"' /><label for='qunit-urlconfig-"+escaped+
"' title='"+escapedTooltip+"'>"+val.label+"</label>";}else{urlConfigHtml+="<label for='qunit-urlconfig-"+escaped+
"' title='"+escapedTooltip+"'>"+val.label+
": </label><select id='qunit-urlconfig-"+escaped+
"' name='"+escaped+"' title='"+escapedTooltip+"'><option></option>";if(QUnit.is("array",val.value)){for(j=0;j<val.value.length;j++){escaped=escapeText(val.value[j]);urlConfigHtml+="<option value='"+escaped+"'"+(config[val.id]===val.value[j]?(selection=true)&&" selected='selected'":"")+
">"+escaped+"</option>";}}else{for(j in val.value){if(hasOwn.call(val.value,j)){urlConfigHtml+="<option value='"+escapeText(j)+"'"+(config[val.id]===j?(selection=true)&&" selected='selected'":"")+
">"+escapeText(val.value[j])+"</option>";}}}
if(config[val.id]&&!selection){escaped=escapeText(config[val.id]);urlConfigHtml+="<option value='"+escaped+
"' selected='selected' disabled='disabled'>"+escaped+"</option>";}
urlConfigHtml+="</select>";}}
return urlConfigHtml;}
function toolbarChanged(){var updatedUrl,value,field=this,params={};//
if("selectedIndex" in field){value=field.options[field.selectedIndex].value||undefined;}else{value=field.checked?(field.defaultValue||true):undefined;}
params[field.name]=value;updatedUrl=setUrl(params);if("hidepassed"===field.name&&"replaceState" in window.history){config[field.name]=value||false;if(value){addClass(id("qunit-tests"),"hidepass");}else{removeClass(id("qunit-tests"),"hidepass");}
window.history.replaceState(null,"",updatedUrl);}else{window.location=updatedUrl;}}
function setUrl(params){var key,querystring="?";params=QUnit.extend(QUnit.extend({},QUnit.urlParams),params);for(key in params){if(hasOwn.call(params,key)){if(params[key]===undefined){continue;}
querystring+=encodeURIComponent(key);if(params[key]!==true){querystring+="="+encodeURIComponent(params[key]);}
querystring+="&";}}
return location.protocol+"//"+location.host+
location.pathname+querystring.slice(0,-1);}
function applyUrlParams(){var selectedModule,modulesList=id("qunit-modulefilter"),filter=id("qunit-filter-input").value;selectedModule=modulesList?decodeURIComponent(modulesList.options[modulesList.selectedIndex].value):undefined;window.location=setUrl({module:(selectedModule==="")?undefined:selectedModule,filter:(filter==="")?undefined:filter,testId:undefined});}
function toolbarUrlConfigContainer(){var urlConfigContainer=document.createElement("span");urlConfigContainer.innerHTML=getUrlConfigHtml();addClass(urlConfigContainer,"qunit-url-config");addEvents(urlConfigContainer.getElementsByTagName("input"),"click",toolbarChanged);addEvents(urlConfigContainer.getElementsByTagName("select"),"change",toolbarChanged);return urlConfigContainer;}
function toolbarLooseFilter(){var filter=document.createElement("form"),label=document.createElement("label"),input=document.createElement("input"),button=document.createElement("button");addClass(filter,"qunit-filter");label.innerHTML="Filter: ";input.type="text";input.value=config.filter||"";input.name="filter";input.id="qunit-filter-input";button.innerHTML="Go";label.appendChild(input);filter.appendChild(label);filter.appendChild(button);addEvent(filter,"submit",function(ev){applyUrlParams();if(ev&&ev.preventDefault){ev.preventDefault();}
return false;});return filter;}
function toolbarModuleFilterHtml(){var i,moduleFilterHtml="";if(!modulesList.length){return false;}
modulesList.sort(function(a,b){return a.localeCompare(b);});moduleFilterHtml+="<label for='qunit-modulefilter'>Module: </label>"+
"<select id='qunit-modulefilter' name='modulefilter'><option value='' "+(QUnit.urlParams.module===undefined?"selected='selected'":"")+
">< All Modules ></option>";for(i=0;i<modulesList.length;i++){moduleFilterHtml+="<option value='"+
escapeText(encodeURIComponent(modulesList[i]))+"' "+(QUnit.urlParams.module===modulesList[i]?"selected='selected'":"")+
">"+escapeText(modulesList[i])+"</option>";}
moduleFilterHtml+="</select>";return moduleFilterHtml;}
function toolbarModuleFilter(){var toolbar=id("qunit-testrunner-toolbar"),moduleFilter=document.createElement("span"),moduleFilterHtml=toolbarModuleFilterHtml();if(!toolbar||!moduleFilterHtml){return false;}
moduleFilter.setAttribute("id","qunit-modulefilter-container");moduleFilter.innerHTML=moduleFilterHtml;addEvent(moduleFilter.lastChild,"change",applyUrlParams);toolbar.appendChild(moduleFilter);}
function appendToolbar(){var toolbar=id("qunit-testrunner-toolbar");if(toolbar){toolbar.appendChild(toolbarUrlConfigContainer());toolbar.appendChild(toolbarLooseFilter());}}
function appendHeader(){var header=id("qunit-header");if(header){header.innerHTML="<a href='"+
setUrl({filter:undefined,module:undefined,testId:undefined})+
"'>"+header.innerHTML+"</a> ";}}
function appendBanner(){var banner=id("qunit-banner");if(banner){banner.className="";}}
function appendTestResults(){var tests=id("qunit-tests"),result=id("qunit-testresult");if(result){result.parentNode.removeChild(result);}
if(tests){tests.innerHTML="";result=document.createElement("p");result.id="qunit-testresult";result.className="result";tests.parentNode.insertBefore(result,tests);result.innerHTML="Running...<br />&#160;";}}
function storeFixture(){var fixture=id("qunit-fixture");if(fixture){config.fixture=fixture.innerHTML;}}
function appendUserAgent(){var userAgent=id("qunit-userAgent");if(userAgent){userAgent.innerHTML="";userAgent.appendChild(document.createTextNode("QUnit "+QUnit.version+"; "+navigator.userAgent));}}
function appendTestsList(modules){var i,l,x,z,test,moduleObj;for(i=0,l=modules.length;i<l;i++){moduleObj=modules[i];if(moduleObj.name){modulesList.push(moduleObj.name);}
for(x=0,z=moduleObj.tests.length;x<z;x++){test=moduleObj.tests[x];appendTest(test.name,test.testId,moduleObj.name);}}}
function appendTest(name,testId,moduleName){var title,rerunTrigger,testBlock,assertList,tests=id("qunit-tests");if(!tests){return;}
title=document.createElement("strong");title.innerHTML=getNameHtml(name,moduleName);rerunTrigger=document.createElement("a");rerunTrigger.innerHTML="Rerun";rerunTrigger.href=setUrl({testId:testId});testBlock=document.createElement("li");testBlock.appendChild(title);testBlock.appendChild(rerunTrigger);testBlock.id="qunit-test-output-"+testId;assertList=document.createElement("ol");assertList.className="qunit-assert-list";testBlock.appendChild(assertList);tests.appendChild(testBlock);}
QUnit.begin(function(details){var qunit=id("qunit");storeFixture();if(qunit){qunit.innerHTML="<h1 id='qunit-header'>"+escapeText(document.title)+"</h1>"+
"<h2 id='qunit-banner'></h2>"+
"<div id='qunit-testrunner-toolbar'></div>"+
"<h2 id='qunit-userAgent'></h2>"+
"<ol id='qunit-tests'></ol>";}
appendHeader();appendBanner();appendTestResults();appendUserAgent();appendToolbar();appendTestsList(details.modules);toolbarModuleFilter();if(qunit&&config.hidepassed){addClass(qunit.lastChild,"hidepass");}});QUnit.done(function(details){var i,key,banner=id("qunit-banner"),tests=id("qunit-tests"),html=["Tests completed in ",details.runtime," milliseconds.<br />","<span class='passed'>",details.passed,"</span> assertions of <span class='total'>",details.total,"</span> passed, <span class='failed'>",details.failed,"</span> failed."].join("");if(banner){banner.className=details.failed?"qunit-fail":"qunit-pass";}
if(tests){id("qunit-testresult").innerHTML=html;}
if(config.altertitle&&defined.document&&document.title){document.title=[(details.failed?"\u2716":"\u2714"),document.title.replace(/^[\u2714\u2716] /i,"")].join(" ");}
if(config.reorder&&defined.sessionStorage&&details.failed===0){for(i=0;i<sessionStorage.length;i++){key=sessionStorage.key(i++);if(key.indexOf("qunit-test-")===0){sessionStorage.removeItem(key);}}}
if(config.scrolltop&&window.scrollTo){window.scrollTo(0,0);}});function getNameHtml(name,module){var nameHtml="";if(module){nameHtml="<span class='module-name'>"+escapeText(module)+"</span>: ";}
nameHtml+="<span class='test-name'>"+escapeText(name)+"</span>";return nameHtml;}
QUnit.testStart(function(details){var running,testBlock,bad;testBlock=id("qunit-test-output-"+details.testId);if(testBlock){testBlock.className="running";}else{appendTest(details.name,details.testId,details.module);}
running=id("qunit-testresult");if(running){bad=QUnit.config.reorder&&defined.sessionStorage&&+sessionStorage.getItem("qunit-test-"+details.module+"-"+details.name);running.innerHTML=(bad?"Rerunning previously failed test: <br />":"Running: <br />")+
getNameHtml(details.name,details.module);}});QUnit.log(function(details){var assertList,assertLi,message,expected,actual,testItem=id("qunit-test-output-"+details.testId);if(!testItem){return;}
message=escapeText(details.message)||(details.result?"okay":"failed");message="<span class='test-message'>"+message+"</span>";message+="<span class='runtime'>@ "+details.runtime+" ms</span>";if(!details.result&&hasOwn.call(details,"expected")){expected=escapeText(QUnit.dump.parse(details.expected));actual=escapeText(QUnit.dump.parse(details.actual));message+="<table><tr class='test-expected'><th>Expected: </th><td><pre>"+
expected+
"</pre></td></tr>";if(actual!==expected){message+="<tr class='test-actual'><th>Result: </th><td><pre>"+
actual+"</pre></td></tr>"+
"<tr class='test-diff'><th>Diff: </th><td><pre>"+
QUnit.diff(expected,actual)+"</pre></td></tr>";}else{if(expected.indexOf("[object Array]")!==-1||expected.indexOf("[object Object]")!==-1){message+="<tr class='test-message'><th>Message: </th><td>"+
"Diff suppressed as the depth of object is more than current max depth ("+
QUnit.config.maxDepth+").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to "+
" run with a higher max depth or <a href='"+setUrl({maxDepth:-1})+"'>"+
"Rerun</a> without max depth.</p></td></tr>";}}
if(details.source){message+="<tr class='test-source'><th>Source: </th><td><pre>"+
escapeText(details.source)+"</pre></td></tr>";}
message+="</table>";}else if(!details.result&&details.source){message+="<table>"+
"<tr class='test-source'><th>Source: </th><td><pre>"+
escapeText(details.source)+"</pre></td></tr>"+
"</table>";}
assertList=testItem.getElementsByTagName("ol")[0];assertLi=document.createElement("li");assertLi.className=details.result?"pass":"fail";assertLi.innerHTML=message;assertList.appendChild(assertLi);});QUnit.testDone(function(details){var testTitle,time,testItem,assertList,good,bad,testCounts,skipped,tests=id("qunit-tests");if(!tests){return;}
testItem=id("qunit-test-output-"+details.testId);assertList=testItem.getElementsByTagName("ol")[0];good=details.passed;bad=details.failed;if(config.reorder&&defined.sessionStorage){if(bad){sessionStorage.setItem("qunit-test-"+details.module+"-"+details.name,bad);}else{sessionStorage.removeItem("qunit-test-"+ details.module+"-"+details.name);}}
if(bad===0){addClass(assertList,"qunit-collapsed");}
testTitle=testItem.firstChild;testCounts=bad?"<b class='failed'>"+bad+"</b>, "+"<b class='passed'>"+good+"</b>, ":"";testTitle.innerHTML+=" <b class='counts'>("+testCounts+
details.assertions.length+")</b>";if(details.skipped){testItem.className="skipped";skipped=document.createElement("em");skipped.className="qunit-skipped-label";skipped.innerHTML="skipped";testItem.insertBefore(skipped,testTitle);}else{addEvent(testTitle,"click",function(){toggleClass(assertList,"qunit-collapsed");});testItem.className=bad?"fail":"pass";time=document.createElement("span");time.className="runtime";time.innerHTML=details.runtime+" ms";testItem.insertBefore(time,assertList);}});if(defined.document){if(document.readyState==="complete"){QUnit.load();}else{addEvent(window,"load",QUnit.load);}}else{config.pageLoaded=true;config.autorun=true;}})();

View File

@@ -0,0 +1,51 @@
"use strict";
function recipeDetailsPageInit(ctx){
$(".customise-btn").click(function(e){
e.preventDefault();
var imgCustomModal = $("#new-custom-image-modal");
if (imgCustomModal.length === 0)
throw("Modal new-custom-image not found");
var recipe = {id: $(this).data('recipe'), name: null}
newCustomImageModalSetRecipes([recipe]);
imgCustomModal.modal('show');
});
$("#add-layer-btn").click(function(){
var btn = $(this);
libtoaster.addRmLayer(ctx.recipe.layer_version,
true,
function (layersList){
var msg = libtoaster.makeLayerAddRmAlertMsg(ctx.recipe.layer_version,
layersList,
true);
libtoaster.showChangeNotification(msg);
var toShow = $("#customise-build-btns");
/* If we have no packages built yet also fade in the build packages
* hint message
*/
if (ctx.recipe.totalPackages === 0){
toShow = toShow.add("#build-to-get-packages-msg");
}
$("#packages-alert").add(btn).fadeOut(function(){
toShow.fadeIn();
});
});
});
/* Trigger a build of your custom image */
$(".build-recipe-btn").click(function(){
libtoaster.startABuild(null, ctx.recipe.name,
function(){
window.location.replace(libtoaster.ctx.projectBuildsUrl);
});
});
}

View File

@@ -0,0 +1,857 @@
'use strict';
function tableInit(ctx){
if (ctx.url.length === 0) {
throw "No url supplied for retreiving data";
}
var tableChromeDone = false;
var tableTotal = 0;
var tableParams = {
limit : 25,
page : 1,
orderby : null,
filter : null,
search : null,
default_orderby: null,
};
var defaultHiddenCols = [];
var table = $("#" + ctx.tableName);
/* if we're loading clean from a url use it's parameters as the default */
var urlParams = libtoaster.parseUrlParams();
/* Merge the tableParams and urlParams object properties */
tableParams = $.extend(tableParams, urlParams);
/* Now fix the types that .extend changed for us */
tableParams.limit = Number(tableParams.limit);
tableParams.page = Number(tableParams.page);
loadData(tableParams);
// clicking on this set of elements removes the search
var clearSearchElements = $('.remove-search-btn-'+ctx.tableName +
', .show-all-'+ctx.tableName);
function loadData(tableParams){
table.trigger("table-loading");
$.ajax({
type: "GET",
url: ctx.url,
data: tableParams,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function(tableData) {
updateTable(tableData);
window.history.replaceState(null, null,
libtoaster.dumpsUrlParams(tableParams));
}
});
}
function updateTable(tableData) {
var tableBody = table.children("tbody");
var pagination = $('#pagination-'+ctx.tableName);
var paginationBtns = pagination.children('ul');
var tableContainer = $("#table-container-"+ctx.tableName);
tableContainer.css("visibility", "hidden");
/* To avoid page re-layout flicker when paging set fixed height */
table.css("padding-bottom", table.height());
/* Reset table components */
tableBody.html("");
paginationBtns.html("");
if (tableParams.search)
clearSearchElements.show();
else
clearSearchElements.hide();
$('.table-count-' + ctx.tableName).text(tableData.total);
tableTotal = tableData.total;
if (tableData.total === 0){
tableContainer.hide();
/* No results caused by a search returning nothing */
if (tableParams.search) {
if ($("#no-results-special-"+ctx.tableName).length > 0) {
/* use this page's special no-results form instead of the default */
$("#no-results-search-input-"+ctx.tableName).val(tableParams.search);
$("#no-results-special-"+ctx.tableName).show();
$("#results-found-"+ctx.tableName).hide();
} else {
$("#new-search-input-"+ctx.tableName).val(tableParams.search);
$("#no-results-"+ctx.tableName).show();
}
}
else {
/* No results caused by there being no data */
$("#empty-state-"+ctx.tableName).show();
}
table.trigger("table-done", [tableData.total, tableParams]);
return;
} else {
tableContainer.show();
$("#no-results-"+ctx.tableName).hide();
$("#empty-state-"+ctx.tableName).hide();
}
setupTableChrome(tableData);
/* Add table data rows */
var column_index;
for (var i in tableData.rows){
/* only display if the column is display-able */
var row = $("<tr></tr>");
column_index = -1;
for (var key_j in tableData.rows[i]){
var td = $("<td></td>");
td.prop("class", key_j);
if (tableData.rows[i][key_j]){
td.html(tableData.rows[i][key_j]);
}
row.append(td);
}
tableBody.append(row);
/* If we have layerbtns then initialise them */
layerBtnsInit();
/* If we have popovers initialise them now */
$('td > a.btn').popover({
html:true,
placement:'left',
container:'body',
trigger:'manual'
}).click(function(e){
$('td > a.btn').not(this).popover('hide');
/* ideally we would use 'toggle' here
* but it seems buggy in our Bootstrap version
*/
$(this).popover('show');
e.stopPropagation();
});
/* enable help information tooltip */
$(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
}
/* Setup the pagination controls */
var start = tableParams.page - 2;
var end = tableParams.page + 2;
var numPages = Math.ceil(tableData.total/tableParams.limit);
if (numPages > 1){
if (tableParams.page < 3)
end = 5;
for (var page_i=1; page_i <= numPages; page_i++){
if (page_i >= start && page_i <= end){
var btn = $('<li><a href="#" class="page">'+page_i+'</a></li>');
if (page_i === tableParams.page){
btn.addClass("active");
}
/* Add the click handler */
btn.click(pageButtonClicked);
paginationBtns.append(btn);
}
}
}
loadColumnsPreference();
table.css("padding-bottom", 0);
tableContainer.css("visibility", "visible");
/* If we have a hash in the url try and highlight that item in the table */
if (window.location.hash){
var highlight = $("table a[name="+window.location.hash.replace('#',''));
if (highlight.length > 0){
highlight.parents("tr").addClass('highlight');
window.scroll(0, highlight.position().top - 50);
}
}
table.trigger("table-done", [tableData.total, tableParams]);
}
function setupTableChrome(tableData){
if (tableChromeDone === true)
return;
var tableHeadRow = table.find("thead > tr");
var editColMenu = $("#table-chrome-"+ctx.tableName).find(".editcol");
tableHeadRow.html("");
editColMenu.html("");
tableParams.default_orderby = tableData.default_orderby;
if (!tableParams.orderby && tableData.default_orderby){
tableParams.orderby = tableData.default_orderby;
}
/* Add table header and column toggle menu */
var column_edit_entries = [];
for (var i in tableData.columns){
var col = tableData.columns[i];
if (col.displayable === false) {
continue;
}
var header = $("<th></th>");
header.prop("class", col.field_name);
/* Setup the help text */
if (col.help_text.length > 0) {
var help_text = $('<span class="glyphicon glyphicon-question-sign get-help"> </span>');
help_text.tooltip({title: col.help_text});
header.append(help_text);
}
/* Setup the orderable title */
if (col.orderable) {
var title = $('<a href=\"#\" ></a>');
title.data('field-name', col.field_name);
title.attr('data-sort-field', col.field_name);
title.text(col.title);
title.click(sortColumnClicked);
header.append(title);
header.append(' <i class="icon-caret-down" style="display:none"></i>');
header.append(' <i class="icon-caret-up" style="display:none"></i>');
/* If we're currently ordered setup the visual indicator */
if (col.field_name === tableParams.orderby ||
'-' + col.field_name === tableParams.orderby){
header.children("a").addClass("sorted");
if (tableParams.orderby.indexOf("-") === -1){
header.find('.icon-caret-down').show();
} else {
header.find('.icon-caret-up').show();
}
}
if (col.field_name === tableData.default_orderby){
title.addClass("default-orderby");
}
} else {
/* Not orderable */
header.css("font-weight", "normal");
header.append('<span class="text-muted">' + col.title + '</span> ');
}
/* Setup the filter button */
if (col.filter_name){
var filterBtn = $('<a href="#" role="button" data-filter-on="' + col.filter_name + '" class="pull-right btn btn-link btn-xs" data-toggle="modal"><i class="glyphicon glyphicon-filter filtered"></i></a>');
filterBtn.data('filter-name', col.filter_name);
filterBtn.prop('id', col.filter_name);
filterBtn.click(filterOpenClicked);
/* If we're currently being filtered setup the visial indicator */
if (tableParams.filter &&
tableParams.filter.match('^'+col.filter_name)) {
filterBtnActive(filterBtn, true);
}
header.append(filterBtn);
}
/* Done making the header now add it */
tableHeadRow.append(header);
/* Now setup the checkbox state and click handler */
var toggler = $('<li><div class="checkbox"><label><input type="checkbox" id="checkbox-'+ col.field_name +'" class="col-toggle" value="'+col.field_name+'" />'+col.title+'</label></div></li>');
var togglerInput = toggler.find("input");
togglerInput.attr("checked","checked");
/* If we can hide the column enable the checkbox action */
if (col.hideable){
togglerInput.click(colToggleClicked);
} else {
toggler.find("label").addClass("text-muted");
toggler.find("label").parent().addClass("disabled");
togglerInput.attr("disabled", "disabled");
}
if (col.hidden) {
defaultHiddenCols.push(col.field_name);
}
/* Gather the Edit Column entries */
column_edit_entries.push({'title':col.title,'html':toggler});
} /* End for each column */
/* Append the sorted Edit Column toggler entries */
column_edit_entries.sort(function(a,b) {return (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0);} );
for (var col in column_edit_entries){
editColMenu.append(column_edit_entries[col].html);
}
tableChromeDone = true;
}
/* Toggles the active state of the filter button */
function filterBtnActive(filterBtn, active){
if (active) {
filterBtn.removeClass("btn-link");
filterBtn.addClass("btn-primary");
filterBtn.tooltip({
html: true,
title: '<button class="btn btn-sm btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>',
placement: 'bottom',
delay: {
hide: 1500,
show: 400,
},
});
} else {
filterBtn.removeClass("btn-primary");
filterBtn.addClass("btn-link");
filterBtn.tooltip('destroy');
}
}
/* Display or hide table columns based on the cookie preference or defaults */
function loadColumnsPreference(){
var cookie_data = $.cookie("cols");
if (cookie_data) {
var cols_hidden = JSON.parse($.cookie("cols"));
/* For each of the columns check if we should hide them
* also update the checked status in the Edit columns menu
*/
$("#"+ctx.tableName+" th").each(function(){
for (var i in cols_hidden){
if ($(this).hasClass(cols_hidden[i])){
table.find("."+cols_hidden[i]).hide();
$("#checkbox-"+cols_hidden[i]).removeAttr("checked");
}
}
});
} else {
/* Disable these columns by default when we have no columns
* user setting.
*/
for (var i in defaultHiddenCols) {
table.find("."+defaultHiddenCols[i]).hide();
$("#checkbox-"+defaultHiddenCols[i]).removeAttr("checked");
}
}
}
/* Apply an ordering to the current table.
*
* 1. Find the column heading matching the sortSpecifier
* 2. Set its up/down arrow and add .sorted
*
* orderby: e.g. "-started_on", "completed_on"
* colHeading: column heading element to activate (by showing the caret
* up/down, depending on sort order); if not set, the correct column
* heading is selected from the DOM using orderby as a key
*/
function applyOrderby(orderby, colHeading) {
if (!orderby) {
return;
}
// We only have one sort at a time so remove existing sort indicators
$("#" + ctx.tableName + " th .icon-caret-down").hide();
$("#" + ctx.tableName + " th .icon-caret-up").hide();
$("#" + ctx.tableName + " th a").removeClass("sorted");
// normalise the orderby so we can use it to find the link we want
// to style
var fieldName = orderby;
if (fieldName.indexOf('-') === 0) {
fieldName = fieldName.slice(1);
}
// find the table header element which corresponds to the sort field
// (if we don't already have it)
if (!colHeading) {
colHeading = $('[data-sort-field="' + fieldName + '"]');
}
colHeading.addClass("sorted");
var parent = colHeading.parent();
if (orderby.indexOf('-') === 0) {
parent.children('.icon-caret-up').show();
}
else {
parent.children('.icon-caret-down').show();
}
tableParams.orderby = orderby;
loadData(tableParams);
}
function sortColumnClicked(e){
e.preventDefault();
/* if we're already sorted sort the other way */
var orderby = $(this).data('field-name');
if (tableParams.orderby === orderby &&
tableParams.orderby.indexOf('-') === -1) {
orderby = '-' + orderby;
}
applyOrderby(orderby, $(this));
}
function pageButtonClicked(e) {
tableParams.page = Number($(this).text());
loadData(tableParams);
/* Stop page jumps when clicking on # links */
e.preventDefault();
}
/* Toggle a table column */
function colToggleClicked (){
var col = $(this).val();
var disabled_cols = [];
if ($(this).prop("checked")) {
table.find("."+col).show();
} else {
table.find("."+col).hide();
// If we're ordered by the column we're hiding remove the order by
// and apply the default one instead
if (col === tableParams.orderby ||
'-' + col === tableParams.orderby){
tableParams.orderby = null;
applyOrderby(tableParams.default_orderby);
}
}
/* Update the cookie with the unchecked columns */
$(".col-toggle").not(":checked").map(function(){
disabled_cols.push($(this).val());
});
$.cookie("cols", JSON.stringify(disabled_cols));
}
/**
* Create the DOM/JS for the client side of a TableFilterActionToggle
* or TableFilterActionDay
*
* filterName: (string) internal name for the filter action
* filterActionData: (object)
* filterActionData.count: (number) The number of items this filter will
* show when selected
*
* NB this triggers a filtervalue event each time its radio button is checked
*/
function createActionRadio(filterName, filterActionData) {
var hasNoRecords = (Number(filterActionData.count) == 0);
var actionStr = '<div class="radio">' +
'<label class="filter-title' +
(hasNoRecords ? ' text-muted' : '') + '"' +
' for="' + filterName + '">' +
'<input type="radio" name="filter"' +
' value="' + filterName + '"';
if (hasNoRecords) {
actionStr += ' disabled="disabled"';
}
actionStr += ' id="' + filterName + '">' +
'<input type="hidden" name="filter_value" value="on"' +
' data-value-for="' + filterName + '">' +
filterActionData.title +
' (' + filterActionData.count + ')' +
'</label>' +
'</div>';
var action = $(actionStr);
// fire the filtervalue event from this action when the radio button
// is active so that the apply button can be enabled
action.find('[type="radio"]').change(function () {
if ($(this).is(':checked')) {
action.trigger('filtervalue', 'on');
}
});
return action;
}
/**
* Create the DOM/JS for the client side of a TableFilterActionDateRange
*
* filterName: (string) internal name for the filter action
* filterValue: (string) from,to date range in format yyyy-mm-dd,yyyy-mm-dd;
* used to select the current values for the from/to datepickers;
* if this is partial (e.g. "yyyy-mm-dd,") only the applicable datepicker
* will have a date pre-selected; if empty, neither will
* filterActionData: (object) data for generating the action's HTML
* filterActionData.title: label for the radio button
* filterActionData.max: (string) maximum date for the pickers, in ISO 8601
* datetime format
* filterActionData.min: (string) minimum date for the pickers, ISO 8601
* datetime
*
* NB this triggers a filtervalue event each time its radio button is checked
*/
function createActionDateRange(filterName, filterValue, filterActionData) {
var action = $('<div class="radio">' +
'<label class="filter-title"' +
' for="' + filterName + '">' +
'<input type="radio" name="filter"' +
' value="' + filterName + '" ' +
' id="' + filterName + '">' +
'<input type="hidden" name="filter_value" value=""' +
' data-value-for="' + filterName + '">' +
filterActionData.title +
'</label>' +
'<div class="form-inline form-group date-filter-controls">' +
'<input type="text" maxlength="10" class="form-control"' +
' data-date-from-for="' + filterName + '">' +
'<span>to</span>' +
'<input type="text" maxlength="10" class="form-control"' +
' data-date-to-for="' + filterName + '">' +
'<span class="help-inline get-help">(yyyy-mm-dd)</span>' +
'</div></div>');
var radio = action.find('[type="radio"]');
var value = action.find('[data-value-for]');
// make the datepickers for the range
var options = {
dateFormat: 'yy-mm-dd',
maxDate: new Date(filterActionData.max),
minDate: new Date(filterActionData.min)
};
// create date pickers, setting currently-selected from and to dates
var selectedFrom = null;
var selectedTo = null;
var selectedFromAndTo = [];
if (filterValue) {
selectedFromAndTo = filterValue.split(',');
}
if (selectedFromAndTo.length == 2) {
selectedFrom = selectedFromAndTo[0];
selectedTo = selectedFromAndTo[1];
}
options.defaultDate = selectedFrom;
var inputFrom =
action.find('[data-date-from-for]').datepicker(options);
inputFrom.val(selectedFrom);
options.defaultDate = selectedTo;
var inputTo =
action.find('[data-date-to-for]').datepicker(options);
inputTo.val(selectedTo);
// set filter_value based on date pickers when
// one of their values changes; if either from or to are unset,
// the new value is null;
// this triggers a 'filter_value-change' event on the action's element,
// which is used to determine the disabled/enabled state of the "Apply"
// button
var changeHandler = function () {
var fromValue = inputFrom.val();
var toValue = inputTo.val();
var newValue = undefined;
if (fromValue !== '' && toValue !== '') {
newValue = fromValue + ',' + toValue;
}
value.val(newValue);
// if this action is selected, fire an event for the new range
if (radio.is(':checked')) {
action.trigger('filtervalue', newValue);
}
};
inputFrom.change(changeHandler);
inputTo.change(changeHandler);
// check the associated radio button on clicking a date picker
var checkRadio = function () {
radio.prop('checked', 'checked');
// checking the radio button this way doesn't cause the "change"
// event to fire, so we manually call the changeHandler
changeHandler();
};
inputFrom.focus(checkRadio);
inputTo.focus(checkRadio);
// selecting a date in a picker constrains the date you can
// set in the other picker
inputFrom.change(function () {
inputTo.datepicker('option', 'minDate', inputFrom.val());
});
inputTo.change(function () {
inputFrom.datepicker('option', 'maxDate', inputTo.val());
});
// checking the radio input causes the "Apply" button disabled state to
// change, depending on which from/to dates are supplied
radio.change(changeHandler);
return action;
}
function filterOpenClicked(){
var filterName = $(this).data('filter-name');
/* We need to pass in the current search so that the filter counts take
* into account the current search term
*/
var params = {
'name' : filterName,
'search': tableParams.search,
'cmd': 'filterinfo',
};
$.ajax({
type: "GET",
url: ctx.url,
data: params,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (filterData) {
/*
filterData structure:
{
title: '<title for the filter popup>',
filter_actions: [
{
title: '<label for radio button inside the popup>',
name: '<name of the filter action>',
count: <number of items this filter will show>,
... additional data for the action ...
}
]
}
each filter_action gets a radio button; the value of this is
set to filterName + ':' + filter_action.name; e.g.
in_current_project:in_project
specifies the "in_project" action of the "in_current_project"
filter
the filterName is set on the column filter icon, and corresponds
to a value in the table's filter map
when the filter popup's "Apply" button is clicked, the
value for the radio button which is checked is passed in the
querystring, along with a filter_value, and applied to the
queryset on the table
*/
var filterActionRadios = $('#filter-actions-' + ctx.tableName);
var filterApplyBtn = $('[data-cat="filter-apply"]');
var setApplyButtonState = function (e, filterActionValue) {
if (filterActionValue !== undefined) {
filterApplyBtn.removeAttr('disabled');
}
else {
filterApplyBtn.attr('disabled', 'disabled');
}
};
$('#filter-modal-title-' + ctx.tableName).text(filterData.title);
filterActionRadios.empty();
// create a radio button + form elements for each action associated
// with the filter on this column of the table
for (var i in filterData.filter_actions) {
var action = null;
var filterActionData = filterData.filter_actions[i];
var filterName = filterData.name + ':' +
filterActionData.action_name;
if (filterActionData.type === 'toggle' ||
filterActionData.type === 'day') {
action = createActionRadio(filterName, filterActionData);
}
else if (filterActionData.type === 'daterange') {
// current values for the from/to dates
var filterValue = tableParams.filter_value;
action = createActionDateRange(
filterName,
filterValue,
filterActionData
);
}
if (action) {
// Setup the current selected filter; default to 'all' if
// no current filter selected
var radioInput = action.find('input[name="filter"]');
if ((tableParams.filter &&
tableParams.filter === radioInput.val()) ||
filterActionData.action_name == 'all') {
radioInput.prop("checked", "checked");
}
filterActionRadios.append(action);
// if the action's filter_value changes but is falsy, disable
// the "Apply" button
action.on('filtervalue', setApplyButtonState);
}
}
$('#filter-modal-'+ctx.tableName).modal('show');
}
});
}
/* Allow pages to trigger reload event */
table.on('reload', function(e, newTableParams){
if (newTableParams)
loadData(newTableParams);
else
loadData(tableParams)
});
$(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
/* Keep the Edit columns menu open after click by eating the event */
$('.dropdown-menu').click(function(e) {
e.stopPropagation();
});
$(".pagesize-"+ctx.tableName).val(tableParams.limit);
/* page size selector */
$(".pagesize-"+ctx.tableName).change(function(e){
tableParams.limit = Number(this.value);
if ((tableParams.page * tableParams.limit) > tableTotal)
tableParams.page = 1;
loadData(tableParams);
/* sync the other selectors on the page */
$(".pagesize-"+ctx.tableName).val(this.value);
e.preventDefault();
});
$("#search-submit-"+ctx.tableName).click(function(e){
e.preventDefault();
var searchTerm = $("#search-input-"+ctx.tableName).val();
tableParams.page = 1;
tableParams.search = searchTerm;
/* If a filter was active we remove it */
if (tableParams.filter) {
var filterBtn = $("#" + tableParams.filter.split(":")[0]);
filterBtnActive(filterBtn, false);
tableParams.filter = null;
}
loadData(tableParams);
});
clearSearchElements.click(function(e){
e.preventDefault();
tableParams.page = 1;
tableParams.search = null;
loadData(tableParams);
$("#search-input-"+ctx.tableName).val("");
$(this).hide();
});
$("#search-input-"+ctx.tableName).keyup(function(e){
if (e.which === 13)
$('#search-submit-'+ctx.tableName).click();
});
/* Stop page jumps when clicking on # links */
$('a[href="#"]').click(function(e){
e.preventDefault();
});
$("#clear-filter-btn-"+ctx.tableName).click(function(e){
e.preventDefault();
var filterBtn = $("#" + tableParams.filter.split(":")[0]);
filterBtnActive(filterBtn, false);
tableParams.filter = null;
loadData(tableParams);
});
$("#filter-modal-form-"+ctx.tableName).submit(function(e){
e.preventDefault();
/* remove active status from all filter buttons so that only one filter
can be active at a time */
$('[data-filter-on]').each(function (index, filterBtn) {
filterBtnActive($(filterBtn), false);
});
// checked radio button
var checkedFilter = $(this).find("input[name='filter']:checked");
tableParams.filter = checkedFilter.val();
// hidden field holding the value for the checked filter
var checkedFilterValue = $(this).find("input[data-value-for='" +
tableParams.filter + "']");
tableParams.filter_value = checkedFilterValue.val();
/* All === remove filter */
if (tableParams.filter.match(":all$")) {
tableParams.filter = null;
tableParams.filter_value = null;
} else {
var filterBtn = $("#" + tableParams.filter.split(":")[0]);
filterBtnActive(filterBtn, true);
}
loadData(tableParams);
$('#filter-modal-'+ctx.tableName).modal('hide');
});
table.on("table-loading", function(){
table.css("opacity", 0.5);
});
table.on("table-done", function(){
table.css("opacity", 1);
})
}

View File

@@ -0,0 +1,177 @@
"use strict";
/* Unit tests for Toaster's JS */
/* libtoaster tests */
QUnit.test("Layer alert notification", function(assert) {
var layer = {
"layerdetailurl":"/toastergui/project/1/layer/22",
"xhrLayerUrl":"/toastergui/xhr_layer/1/9",
"vcs_url":"git://example.com/example.git",
"detail":"[ git://example.com/example.git | master ]",
"vcs_reference":"master",
"id": 22,
"name":"meta-example"
};
var layerDepsList = [
{
"layerdetailurl":"/toastergui/project/1/layer/9",
"xhrLayerUrl":"/toastergui/xhr_layer/1/9",
"vcs_url":"git://example.com/example.git",
"detail":"[ git://example.com/example.git | master ]",
"vcs_reference":"master",
"id": 9,
"name":"meta-example-two"
},
{
"layerdetailurl":"/toastergui/project/1/layer/9",
"xhrLayerUrl":"/toastergui/xhr_layer/1/9",
"vcs_url":"git://example.com/example.git",
"detail":"[ git://example.com/example.git | master ]",
"vcs_reference":"master",
"id": 10,
"name":"meta-example-three"
},
];
var msg = libtoaster.makeLayerAddRmAlertMsg(layer, layerDepsList, true);
var test = $("<div></div>");
test.html(msg);
assert.equal(test.children("strong").text(), "3");
assert.equal(test.children("a").length, 3);
});
QUnit.test("Project info", function(assert){
var done = assert.async();
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
assert.ok(prjInfo.machine.name);
assert.ok(prjInfo.layers.length > 0);
assert.ok(prjInfo.freqtargets);
assert.ok(prjInfo.release);
done();
});
});
QUnit.test("Show notification", function(assert){
var msg = "Testing";
var element = $("#change-notification-msg");
libtoaster.showChangeNotification(msg);
assert.equal(element.text(), msg);
assert.ok(element.is(":visible"));
$("#change-notification").hide();
});
var layer = {
"id": 1,
"name": "meta-testing",
"layerdetailurl": "/toastergui/project/1/layer/1",
"xhrLayerUrl": "/toastergui/xhr_layer/1/1"
};
QUnit.test("Add layer", function(assert){
var done = assert.async();
/* Wait for the modal to be added to the dom */
var checkModal = setInterval(function(){
if ($("#dependencies-modal").length > 0) {
$("#dependencies-modal .btn-primary").click();
clearInterval(checkModal);
}
}, 200);
/* Compare the number of layers before and after the add in the project */
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
var origNumLayers = prjInfo.layers.length;
libtoaster.addRmLayer(layer, true, function(deps){
libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl,
function(prjInfo){
assert.ok(prjInfo.layers.length > origNumLayers,
"Layer not added to project");
done();
});
});
});
});
QUnit.test("Rm layer", function(assert){
var done = assert.async();
libtoaster.addRmLayer(layer, false, function(deps){
assert.equal(deps.length, 0);
done();
});
});
QUnit.test("Parse url params", function(assert){
var params = libtoaster.parseUrlParams();
assert.ok(params);
});
QUnit.test("Dump url params", function(assert){
var params = libtoaster.dumpsUrlParams();
assert.ok(params);
});
QUnit.test("Make typeaheads", function(assert){
var layersT = $("#layers");
var machinesT = $("#machines");
var projectsT = $("#projects");
var recipesT = $("#recipes");
libtoaster.makeTypeahead(layersT,
libtoaster.ctx.layersTypeAheadUrl, {}, function(){});
libtoaster.makeTypeahead(machinesT,
libtoaster.ctx.machinesTypeAheadUrl, {}, function(){});
libtoaster.makeTypeahead(projectsT,
libtoaster.ctx.projectsTypeAheadUrl, {}, function(){});
libtoaster.makeTypeahead(recipesT,
libtoaster.ctx.recipesTypeAheadUrl, {}, function(){});
assert.ok(recipesT.data('ttTypeahead'));
assert.ok(layersT.data('ttTypeahead'));
assert.ok(projectsT.data('ttTypeahead'));
assert.ok(recipesT.data('ttTypeahead'));
});
/* Page init functions */
QUnit.test("Import layer page init", function(assert){
assert.throws(importLayerPageInit({ xhrGitRevTypeAheadUrl: "url" }));
});
QUnit.test("Project page init", function(assert){
assert.throws(projectPageInit());
});
QUnit.test("Layer details page init", function(assert){
assert.throws(layerDetailsPageInit());
});
QUnit.test("Layer btns init", function(assert){
assert.throws(layerBtnsInit());
});
QUnit.test("Table init", function(assert){
assert.throws(tableInit({ url : ctx.tableUrl }));
});
$(document).ajaxError(function(event, jqxhr, settings, errMsg){
if (errMsg === 'abort')
return;
QUnit.test("Ajax error", function(assert){
assert.notOk(jqxhr.responseText);
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long