blob: e55fffcef5b1f854d912981023c31cd66136c522 [file] [log] [blame]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001"use strict";
2
3/*
4Used for the newcustomimage_modal actions
5
6The .data('recipe') value on the outer element determines which
7recipe ID is used as the basis for the new custom image recipe created via
8this modal.
9
10Use newCustomImageModalSetRecipes() to set the recipes available as a base
11for the new custom image. This will manage the addition of radio buttons
12to select the base image (or remove the radio buttons, if there is only a
13single base image available).
14*/
15
16function newCustomImageModalInit(){
17
18 var newCustomImgBtn = $("#create-new-custom-image-btn");
19 var imgCustomModal = $("#new-custom-image-modal");
20 var invalidNameHelp = $("#invalid-name-help");
21 var invalidRecipeHelp = $("#invalid-recipe-help");
22 var nameInput = imgCustomModal.find('input');
23
24 var invalidNameMsg = "Image names cannot contain spaces or capital letters. The only allowed special character is dash (-).";
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025 var duplicateNameMsg = "An image with this name already exists. Image names must be unique.";
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050026 var duplicateImageInProjectMsg = "An image with this name already exists in this project."
27 var invalidBaseRecipeIdMsg = "Please select an image to customise.";
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080028 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";
29 var unknownError = "Unexpected error: ";
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050030
Patrick Williamsc0f7c042017-02-23 20:41:17 -060031 // set button to "submit" state and enable text entry so user can
32 // enter the custom recipe name
33 showSubmitState();
34
35 /* capture clicks on radio buttons inside the modal; when one is selected,
36 * set the recipe on the modal
37 */
38 imgCustomModal.on("click", "[name='select-image']", function(e) {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039 clearRecipeError();
Patrick Williamsc0f7c042017-02-23 20:41:17 -060040 $(".radio").each(function(){
41 $(this).removeClass("has-error");
42 });
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043
44 var recipeId = $(e.target).attr('data-recipe');
45 imgCustomModal.data('recipe', recipeId);
46 });
47
48 newCustomImgBtn.click(function(e){
Patrick Williamsc0f7c042017-02-23 20:41:17 -060049 // disable the button and text entry
50 showLoadingState();
51
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050052 e.preventDefault();
53
54 var baseRecipeId = imgCustomModal.data('recipe');
55
56 if (!baseRecipeId) {
57 showRecipeError(invalidBaseRecipeIdMsg);
Patrick Williamsc0f7c042017-02-23 20:41:17 -060058 $(".radio").each(function(){
59 $(this).addClass("has-error");
60 });
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050061 return;
62 }
63
64 if (nameInput.val().length > 0) {
65 libtoaster.createCustomRecipe(nameInput.val(), baseRecipeId,
66 function(ret) {
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080067 showSubmitState();
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050068 if (ret.error !== "ok") {
69 console.warn(ret.error);
70 if (ret.error === "invalid-name") {
71 showNameError(invalidNameMsg);
72 return;
73 } else if (ret.error === "recipe-already-exists") {
74 showNameError(duplicateNameMsg);
75 return;
76 } else if (ret.error === "image-already-exists") {
77 showNameError(duplicateImageInProjectMsg);
78 return;
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080079 } else if (ret.error === "recipe-parent-not-exist") {
80 showNameError(missingParentRecipe);
81 } else {
82 showNameError(unknownError + ret.error);
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050083 }
84 } else {
85 imgCustomModal.modal('hide');
Patrick Williamsc0f7c042017-02-23 20:41:17 -060086 imgCustomModal.one('hidden.bs.modal', showSubmitState);
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050087 window.location.replace(ret.url + '?notify=new');
88 }
89 });
90 }
91 });
92
Patrick Williamsc0f7c042017-02-23 20:41:17 -060093 // enable text entry, show "Create image" button text
94 function showSubmitState() {
95 libtoaster.enableAjaxLoadingTimer();
96 newCustomImgBtn.find('[data-role="loading-state"]').hide();
97 newCustomImgBtn.find('[data-role="submit-state"]').show();
98 newCustomImgBtn.removeAttr('disabled');
99 nameInput.removeAttr('disabled');
100 }
101
102 // disable text entry, show "Creating image..." button text;
103 // we also disabled the page-level ajax loading spinner while this spinner
104 // is active
105 function showLoadingState() {
106 libtoaster.disableAjaxLoadingTimer();
107 newCustomImgBtn.find('[data-role="submit-state"]').hide();
108 newCustomImgBtn.find('[data-role="loading-state"]').show();
109 newCustomImgBtn.attr('disabled', 'disabled');
110 nameInput.attr('disabled', 'disabled');
111 }
112
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500113 function showNameError(text){
114 invalidNameHelp.text(text);
115 invalidNameHelp.show();
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600116 nameInput.parent().addClass('has-error');
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500117 }
118
119 function showRecipeError(text){
120 invalidRecipeHelp.text(text);
121 invalidRecipeHelp.show();
122 }
123
124 function clearRecipeError(){
125 invalidRecipeHelp.hide();
126 }
127
128 nameInput.on('keyup', function(){
129 if (nameInput.val().length === 0){
130 newCustomImgBtn.prop("disabled", true);
131 return
132 }
133
134 if (nameInput.val().search(/[^a-z|0-9|-]/) != -1){
135 showNameError(invalidNameMsg);
136 newCustomImgBtn.prop("disabled", true);
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600137 nameInput.parent().addClass('has-error');
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500138 } else {
139 invalidNameHelp.hide();
140 newCustomImgBtn.prop("disabled", false);
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600141 nameInput.parent().removeClass('has-error');
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500142 }
143 });
144}
145
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600146/* Set the image recipes which can used as the basis for the custom
147 * image recipe the user is creating
148 * baseRecipes: a list of one or more recipes which can be
149 * used as the base for the new custom image recipe in the format:
150 * [{'id': <recipe ID>, 'name': <recipe name>'}, ...]
151 *
152 * if recipes is a single recipe, just show the text box to set the
153 * name for the new custom image; if recipes contains multiple recipe objects,
154 * show a set of radio buttons so the user can decide which to use as the
155 * basis for the new custom image
156 */
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500157function newCustomImageModalSetRecipes(baseRecipes) {
158 var imgCustomModal = $("#new-custom-image-modal");
159 var imageSelector = $('#new-custom-image-modal [data-role="image-selector"]');
160 var imageSelectRadiosContainer = $('#new-custom-image-modal [data-role="image-selector-radios"]');
161
162 // remove any existing radio buttons + labels
163 imageSelector.remove('[data-role="image-radio"]');
164
165 if (baseRecipes.length === 1) {
166 // hide the radio button container
167 imageSelector.hide();
168
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600169 /* set the single recipe ID on the modal as it's the only one
170 * we can build from.
171 */
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500172 imgCustomModal.data('recipe', baseRecipes[0].id);
173 }
174 else {
175 // add radio buttons; note that the handlers for the radio buttons
176 // are set in newCustomImageModalInit via event delegation
177 for (var i = 0; i < baseRecipes.length; i++) {
178 var recipe = baseRecipes[i];
179 imageSelectRadiosContainer.append(
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180 '<div class="radio"><label data-role="image-radio">' +
181 '<input type="radio" name="select-image" ' +
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500182 'data-recipe="' + recipe.id + '">' +
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600183 recipe.name +
184 '</label></div>'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500185 );
186 }
187
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600188 /* select the first radio button as default selection. Radio button
189 * groups should always display with an option checked
190 */
191 imageSelectRadiosContainer.find("input:radio:first").attr("checked", "checked");
192
193 /* check which radio button is selected by default inside the modal,
194 * and set the recipe on the modal accordingly
195 */
196 imageSelectRadiosContainer.find("input:radio").each(function(){
197 if ( $(this).is(":checked") ) {
198 var recipeId = $(this).attr("data-recipe");
199 imgCustomModal.data("recipe", recipeId);
200 }
201 });
202
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500203 // show the radio button container
204 imageSelector.show();
205 }
206}