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