1"use strict" 2 3function importLayerPageInit (ctx) { 4 5 var layerDepBtn = $("#add-layer-dependency-btn"); 6 var importAndAddBtn = $("#import-and-add-btn"); 7 var layerNameInput = $("#import-layer-name"); 8 var vcsURLInput = $("#layer-git-repo-url"); 9 var gitRefInput = $("#layer-git-ref"); 10 var layerDepInput = $("#layer-dependency"); 11 var layerNameCtrl = $("#layer-name-ctrl"); 12 var duplicatedLayerName = $("#duplicated-layer-name-hint"); 13 var localDirPath = $("#local-dir-path"); 14 15 var layerDeps = {}; 16 var layerDepsDeps = {}; 17 var currentLayerDepSelection; 18 var validLayerName = /^(\w|-)+$/; 19 20 /* Catch 'disable' race condition between type-ahead started and "input change" */ 21 var typeAheadStarted = 0; 22 23 libtoaster.makeTypeahead(layerDepInput, 24 libtoaster.ctx.layersTypeAheadUrl, 25 { include_added: "true" }, function(item){ 26 currentLayerDepSelection = item; 27 layerDepBtn.removeAttr("disabled"); 28 typeAheadStarted = 1; 29 }); 30 31 layerDepInput.on("typeahead:select", function(event, data){ 32 currentLayerDepSelection = data; 33 }); 34 35 // Disable local dir repo when page is loaded. 36 $('#local-dir').hide(); 37 38 // disable the "Add layer" button when the layer input typeahead is empty 39 // or not in the typeahead choices 40 layerDepInput.on("input change", function(){ 41 if (0 == typeAheadStarted) { 42 layerDepBtn.attr("disabled","disabled"); 43 } 44 typeAheadStarted = 0; 45 }); 46 47 /* We automatically add "openembedded-core" layer for convenience as a 48 * dependency as pretty much all layers depend on this one 49 */ 50 $.getJSON(libtoaster.ctx.layersTypeAheadUrl, 51 { include_added: "true" , search: "openembedded-core" }, 52 function(layer) { 53 if (layer.results.length > 0) { 54 currentLayerDepSelection = layer.results[0]; 55 layerDepBtn.click(); 56 } 57 }); 58 59 layerDepBtn.click(function(){ 60 typeAheadStarted = 0; 61 if (currentLayerDepSelection == undefined) 62 return; 63 64 layerDeps[currentLayerDepSelection.id] = currentLayerDepSelection; 65 66 /* Make a list item for the new layer dependency */ 67 var newLayerDep = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Remove\"></span></li>"); 68 69 newLayerDep.data('layer-id', currentLayerDepSelection.id); 70 newLayerDep.children("span").tooltip(); 71 72 var link = newLayerDep.children("a"); 73 link.attr("href", currentLayerDepSelection.layerdetailurl); 74 link.text(currentLayerDepSelection.name); 75 link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"}); 76 77 var trashItem = newLayerDep.children("span"); 78 trashItem.click(function () { 79 var toRemove = $(this).parent().data('layer-id'); 80 delete layerDeps[toRemove]; 81 $(this).parent().fadeOut(function (){ 82 $(this).remove(); 83 }); 84 }); 85 86 $("#layer-deps-list").append(newLayerDep); 87 88 libtoaster.getLayerDepsForProject(currentLayerDepSelection.xhrLayerUrl, 89 function (data){ 90 /* These are the dependencies of the layer added as a dependency */ 91 if (data.list.length > 0) { 92 currentLayerDepSelection.url = currentLayerDepSelection.layerdetailurl; 93 layerDeps[currentLayerDepSelection.id].deps = data.list; 94 } 95 96 /* Clear the current selection */ 97 layerDepInput.val(""); 98 currentLayerDepSelection = undefined; 99 layerDepBtn.attr("disabled","disabled"); 100 }, null); 101 }); 102 103 importAndAddBtn.click(function(e){ 104 e.preventDefault(); 105 /* This is a list of the names from layerDeps for the layer deps 106 * modal dialog body 107 */ 108 var depNames = []; 109 110 /* arrray of all layer dep ids includes parent and child deps */ 111 var allDeps = []; 112 113 /* temporary object to use to do a reduce on the dependencies for each 114 * layer dependency added 115 */ 116 var depDeps = {}; 117 118 /* the layers that have dependencies have an extra property "deps" 119 * look in this for each layer and reduce this to a unquie object 120 * of deps. 121 */ 122 for (var key in layerDeps){ 123 if (layerDeps[key].hasOwnProperty('deps')){ 124 for (var dep in layerDeps[key].deps){ 125 var layer = layerDeps[key].deps[dep]; 126 depDeps[layer.id] = layer; 127 } 128 } 129 depNames.push(layerDeps[key].name); 130 allDeps.push(layerDeps[key].id); 131 } 132 133 /* we actually want it as an array so convert it now */ 134 var depDepsArray = []; 135 for (var key in depDeps) 136 depDepsArray.push (depDeps[key]); 137 138 if (depDepsArray.length > 0) { 139 var layer = { name: layerNameInput.val(), url: "#", id: -1 }; 140 var title = "Layer"; 141 var body = "<strong>"+layer.name+"</strong>'s dependencies ("+ 142 depNames.join(", ")+"</span>) require some layers that are not added to your project. Select the ones you want to add:</p>"; 143 144 showLayerDepsModal(layer, 145 depDepsArray, 146 title, body, false, function(layerObsList){ 147 /* Add the accepted layer dependencies' ids to the allDeps array */ 148 for (var key in layerObsList){ 149 allDeps.push(layerObsList[key].id); 150 } 151 import_and_add (); 152 }); 153 } else { 154 import_and_add (); 155 } 156 157 function import_and_add () { 158 /* convert to a csv of all the deps to be added */ 159 var layerDepsCsv = allDeps.join(","); 160 161 var layerData = { 162 name: layerNameInput.val(), 163 vcs_url: vcsURLInput.val(), 164 git_ref: gitRefInput.val(), 165 dir_path: $("#layer-subdir").val(), 166 project_id: libtoaster.ctx.projectId, 167 layer_deps: layerDepsCsv, 168 local_source_dir: $('#local-dir-path').val(), 169 add_to_project: true, 170 }; 171 172 if ($('input[name=repo]:checked').val() == "git") { 173 layerData.local_source_dir = ""; 174 } else { 175 layerData.vcs_url = ""; 176 layerData.git_ref = ""; 177 } 178 179 $.ajax({ 180 type: "PUT", 181 url: ctx.xhrLayerUrl, 182 data: JSON.stringify(layerData), 183 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 184 success: function (data) { 185 if (data.error != "ok") { 186 console.log(data.error); 187 /* let the user know why nothing happened */ 188 alert(data.error) 189 } else { 190 createImportedNotification(data); 191 window.location.replace(libtoaster.ctx.projectPageUrl); 192 } 193 }, 194 error: function (data) { 195 console.log("Call failed"); 196 console.log(data); 197 } 198 }); 199 } 200 }); 201 202 /* Layer imported notification */ 203 function createImportedNotification(imported){ 204 var message = "Layer imported"; 205 206 if (imported.deps_added.length === 0) { 207 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."; 208 } else { 209 210 var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, "; 211 212 imported.deps_added.map (function(item, index){ 213 links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>'; 214 /*If we're at the last element we don't want the trailing comma */ 215 if (imported.deps_added[index+1] !== undefined) 216 links += ', '; 217 }); 218 219 /* Length + 1 here to do deps + the imported layer */ 220 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>'; 221 } 222 223 libtoaster.setNotification("layer-imported", message); 224 } 225 226 function enable_import_btn(enabled) { 227 var importAndAddHint = $("#import-and-add-hint"); 228 229 if (enabled) { 230 importAndAddBtn.removeAttr("disabled"); 231 importAndAddHint.hide(); 232 return; 233 } 234 235 importAndAddBtn.attr("disabled", "disabled"); 236 importAndAddHint.show(); 237 } 238 239 function check_form() { 240 var valid = false; 241 var inputs = $("input:required"); 242 var inputStr = inputs.val().split(""); 243 244 for (var i=0; i<inputs.val().length; i++){ 245 if (!(valid = inputStr[i])){ 246 enable_import_btn(false); 247 break; 248 } 249 } 250 251 if (valid) { 252 if ($("#local-dir-radio").prop("checked") && 253 localDirPath.val().length > 0) { 254 enable_import_btn(true); 255 } 256 257 if ($("#git-repo-radio").prop("checked")) { 258 if (gitRefInput.val().length > 0 && 259 gitRefInput.val() == 'HEAD') { 260 $('#invalid-layer-revision-hint').show(); 261 $('#layer-revision-ctrl').addClass('has-error'); 262 enable_import_btn(false); 263 } else if (vcsURLInput.val().length > 0 && 264 gitRefInput.val().length > 0) { 265 $('#invalid-layer-revision-hint').hide(); 266 $('#layer-revision-ctrl').removeClass('has-error'); 267 enable_import_btn(true); 268 } 269 } 270 } 271 272 if (inputs.val().length == 0) 273 enable_import_btn(false); 274 } 275 276 function layerExistsError(layer){ 277 var dupLayerInfo = $("#duplicate-layer-info"); 278 279 if (layer.local_source_dir) { 280 $("#git-layer-dup").hide(); 281 $("#local-layer-dup").fadeIn(); 282 dupLayerInfo.find(".dup-layer-name").text(layer.name); 283 dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl); 284 dupLayerInfo.find("#dup-local-source-dir-name").text(layer.local_source_dir); 285 } else { 286 $("#git-layer-dup").fadeIn(); 287 $("#local-layer-dup").hide(); 288 dupLayerInfo.find(".dup-layer-name").text(layer.name); 289 dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl); 290 dupLayerInfo.find("#dup-layer-vcs-url").text(layer.vcs_url); 291 dupLayerInfo.find("#dup-layer-revision").text(layer.vcs_reference); 292 } 293 $(".fields-apart-from-layer-name").fadeOut(function(){ 294 295 dupLayerInfo.fadeIn(); 296 }); 297 } 298 299 layerNameInput.on('blur', function() { 300 if (!$(this).val()){ 301 return; 302 } 303 var name = $(this).val(); 304 305 /* Check if the layer name exists */ 306 $.getJSON(libtoaster.ctx.layersTypeAheadUrl, 307 { include_added: "true" , search: name, format: "json" }, 308 function(layer) { 309 if (layer.results.length > 0) { 310 for (var i in layer.results){ 311 if (layer.results[i].name == name) { 312 layerExistsError(layer.results[i]); 313 } 314 } 315 } 316 }); 317 }); 318 319 vcsURLInput.on('input', function() { 320 check_form(); 321 }); 322 323 gitRefInput.on('input', function() { 324 check_form(); 325 }); 326 327 layerNameInput.on('input', function() { 328 if ($(this).val() && !validLayerName.test($(this).val())){ 329 layerNameCtrl.addClass("has-error") 330 $("#invalid-layer-name-hint").show(); 331 enable_import_btn(false); 332 return; 333 } 334 335 if ($("#duplicate-layer-info").css("display") != "None"){ 336 $("#duplicate-layer-info").fadeOut(function(){ 337 $(".fields-apart-from-layer-name").show(); 338 radioDisplay(); 339 }); 340 341 } 342 343 radioDisplay(); 344 345 /* Don't remove the error class if we're displaying the error for another 346 * reason. 347 */ 348 if (!duplicatedLayerName.is(":visible")) 349 layerNameCtrl.removeClass("has-error") 350 351 $("#invalid-layer-name-hint").hide(); 352 check_form(); 353 }); 354 355 /* Setup 'blank' typeahead */ 356 libtoaster.makeTypeahead(gitRefInput, 357 ctx.xhrGitRevTypeAheadUrl, 358 { git_url: null }, function(){}); 359 360 361 vcsURLInput.focusout(function (){ 362 if (!$(this).val()) 363 return; 364 365 /* If we a layer name specified don't overwrite it or if there isn't a 366 * url typed in yet return 367 */ 368 if (!layerNameInput.val() && $(this).val().search("/")){ 369 var urlPts = $(this).val().split("/"); 370 /* Add a suggestion of the layer name */ 371 var suggestion = urlPts[urlPts.length-1].replace(".git",""); 372 layerNameInput.val(suggestion); 373 } 374 375 /* Now actually setup the typeahead properly with the git url entered */ 376 gitRefInput._typeahead('destroy'); 377 378 libtoaster.makeTypeahead(gitRefInput, 379 ctx.xhrGitRevTypeAheadUrl, 380 { git_url: $(this).val() }, 381 function(selected){ 382 gitRefInput._typeahead("close"); 383 }); 384 385 }); 386 387 function radioDisplay() { 388 if ($('input[name=repo]:checked').val() == "local") { 389 $('#git-repo').hide(); 390 $('#import-git-layer-and-add-hint').hide(); 391 $('#local-dir').fadeIn(); 392 $('#import-local-dir-and-add-hint').fadeIn(); 393 } else { 394 $('#local-dir').hide(); 395 $('#import-local-dir-and-add-hint').hide(); 396 $('#git-repo').fadeIn(); 397 $('#import-git-layer-and-add-hint').fadeIn(); 398 } 399 } 400 401 $('input:radio[name="repo"]').change(function() { 402 radioDisplay(); 403 if ($("#local-dir-radio").prop("checked")) { 404 if (localDirPath.val().length > 0) { 405 enable_import_btn(true); 406 } else { 407 enable_import_btn(false); 408 } 409 } 410 if ($("#git-repo-radio").prop("checked")) { 411 if (vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) { 412 enable_import_btn(true); 413 } else { 414 enable_import_btn(false); 415 } 416 } 417 }); 418 419 localDirPath.on('input', function(){ 420 if ($(this).val().trim().length == 0) { 421 $('#import-and-add-btn').attr("disabled","disabled"); 422 $('#local-dir').addClass('has-error'); 423 $('#hintError-dir-abs-path').show(); 424 $('#hintError-dir-path-starts-with-slash').show(); 425 } else { 426 var input = $(this); 427 var reBeginWithSlash = /^\//; 428 var reCheckVariable = /^\$/; 429 var re = /([ <>\\|":%\?\*]+)/; 430 431 var invalidDir = re.test(input.val()); 432 var invalidSlash = reBeginWithSlash.test(input.val()); 433 var invalidVar = reCheckVariable.test(input.val()); 434 435 if (!invalidSlash && !invalidVar) { 436 $('#local-dir').addClass('has-error'); 437 $('#import-and-add-btn').attr("disabled","disabled"); 438 $('#hintError-dir-abs-path').show(); 439 $('#hintError-dir-path-starts-with-slash').show(); 440 } else if (invalidDir) { 441 $('#local-dir').addClass('has-error'); 442 $('#import-and-add-btn').attr("disabled","disabled"); 443 $('#hintError-dir-path').show(); 444 } else { 445 $('#local-dir').removeClass('has-error'); 446 if (layerNameInput.val().length > 0) { 447 $('#import-and-add-btn').removeAttr("disabled"); 448 } 449 $('#hintError-dir-abs-path').hide(); 450 $('#hintError-dir-path-starts-with-slash').hide(); 451 $('#hintError-dir-path').hide(); 452 } 453 } 454 }); 455} 456