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