Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1 | "use strict"; |
| 2 | |
| 3 | /* |
| 4 | Used for the newcustomimage_modal actions |
| 5 | |
| 6 | The .data('recipe') value on the outer element determines which |
| 7 | recipe ID is used as the basis for the new custom image recipe created via |
| 8 | this modal. |
| 9 | |
| 10 | Use newCustomImageModalSetRecipes() to set the recipes available as a base |
| 11 | for the new custom image. This will manage the addition of radio buttons |
| 12 | to select the base image (or remove the radio buttons, if there is only a |
| 13 | single base image available). |
| 14 | */ |
| 15 | |
| 16 | function 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 Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 25 | var duplicateNameMsg = "An image with this name already exists. Image names must be unique."; |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 26 | var duplicateImageInProjectMsg = "An image with this name already exists in this project." |
| 27 | var invalidBaseRecipeIdMsg = "Please select an image to customise."; |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 28 | 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 Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 30 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 31 | // 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 Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 39 | clearRecipeError(); |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 40 | $(".radio").each(function(){ |
| 41 | $(this).removeClass("has-error"); |
| 42 | }); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 43 | |
| 44 | var recipeId = $(e.target).attr('data-recipe'); |
| 45 | imgCustomModal.data('recipe', recipeId); |
| 46 | }); |
| 47 | |
| 48 | newCustomImgBtn.click(function(e){ |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 49 | // disable the button and text entry |
| 50 | showLoadingState(); |
| 51 | |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 52 | e.preventDefault(); |
| 53 | |
| 54 | var baseRecipeId = imgCustomModal.data('recipe'); |
| 55 | |
| 56 | if (!baseRecipeId) { |
| 57 | showRecipeError(invalidBaseRecipeIdMsg); |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 58 | $(".radio").each(function(){ |
| 59 | $(this).addClass("has-error"); |
| 60 | }); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 61 | return; |
| 62 | } |
| 63 | |
| 64 | if (nameInput.val().length > 0) { |
| 65 | libtoaster.createCustomRecipe(nameInput.val(), baseRecipeId, |
| 66 | function(ret) { |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 67 | showSubmitState(); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 68 | 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 Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 79 | } else if (ret.error === "recipe-parent-not-exist") { |
| 80 | showNameError(missingParentRecipe); |
| 81 | } else { |
| 82 | showNameError(unknownError + ret.error); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 83 | } |
| 84 | } else { |
| 85 | imgCustomModal.modal('hide'); |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 86 | imgCustomModal.one('hidden.bs.modal', showSubmitState); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 87 | window.location.replace(ret.url + '?notify=new'); |
| 88 | } |
| 89 | }); |
| 90 | } |
| 91 | }); |
| 92 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 93 | // 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 Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 113 | function showNameError(text){ |
| 114 | invalidNameHelp.text(text); |
| 115 | invalidNameHelp.show(); |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 116 | nameInput.parent().addClass('has-error'); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 117 | } |
| 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 Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 137 | nameInput.parent().addClass('has-error'); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 138 | } else { |
| 139 | invalidNameHelp.hide(); |
| 140 | newCustomImgBtn.prop("disabled", false); |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 141 | nameInput.parent().removeClass('has-error'); |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 142 | } |
| 143 | }); |
| 144 | } |
| 145 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 146 | /* 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 Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 157 | function 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 Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 169 | /* set the single recipe ID on the modal as it's the only one |
| 170 | * we can build from. |
| 171 | */ |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 172 | 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 Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 180 | '<div class="radio"><label data-role="image-radio">' + |
| 181 | '<input type="radio" name="select-image" ' + |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 182 | 'data-recipe="' + recipe.id + '">' + |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 183 | recipe.name + |
| 184 | '</label></div>' |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 185 | ); |
| 186 | } |
| 187 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 188 | /* 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 Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 203 | // show the radio button container |
| 204 | imageSelector.show(); |
| 205 | } |
| 206 | } |