diff options
Diffstat (limited to 'app/assets/javascripts/releases/components')
-rw-r--r-- | app/assets/javascripts/releases/components/app_edit_new.vue (renamed from app/assets/javascripts/releases/components/app_edit.vue) | 86 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/app_new.vue | 9 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/asset_links_form.vue | 14 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/form_field_container.vue | 12 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/release_block_assets.vue | 4 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/release_block_author.vue | 2 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/release_block_milestone_info.vue | 2 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/tag_field.vue | 20 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/tag_field_existing.vue | 51 | ||||
-rw-r--r-- | app/assets/javascripts/releases/components/tag_field_new.vue | 100 |
10 files changed, 229 insertions, 71 deletions
diff --git a/app/assets/javascripts/releases/components/app_edit.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index 01dd0638023..7b7c80a6269 100644 --- a/app/assets/javascripts/releases/components/app_edit.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -1,18 +1,17 @@ <script> import { mapState, mapActions, mapGetters } from 'vuex'; import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; -import { escape } from 'lodash'; import { __, sprintf } from '~/locale'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import { BACK_URL_PARAM } from '~/releases/constants'; import { getParameterByName } from '~/lib/utils/common_utils'; import AssetLinksForm from './asset_links_form.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue'; +import TagField from './tag_field.vue'; export default { - name: 'ReleaseEditApp', + name: 'ReleaseEditNewApp', components: { GlFormInput, GlFormGroup, @@ -20,9 +19,7 @@ export default { MarkdownField, AssetLinksForm, MilestoneCombobox, - }, - directives: { - autofocusonshow, + TagField, }, mixins: [glFeatureFlagsMixin()], computed: { @@ -39,9 +36,9 @@ export default { 'manageMilestonesPath', 'projectId', ]), - ...mapGetters('detail', ['isValid']), + ...mapGetters('detail', ['isValid', 'isExistingRelease']), showForm() { - return !this.isFetchingRelease && !this.fetchError; + return Boolean(!this.isFetchingRelease && !this.fetchError && this.release); }, subtitleText() { return sprintf( @@ -55,23 +52,6 @@ export default { false, ); }, - tagName() { - return this.$store.state.detail.release.tagName; - }, - tagNameHintText() { - return sprintf( - __( - 'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}', - ), - { - linkStart: `<a href="${escape( - this.updateReleaseApiDocsPath, - )}" target="_blank" rel="noopener noreferrer">`, - linkEnd: '</a>', - }, - false, - ); - }, releaseTitle: { get() { return this.$store.state.detail.release.name; @@ -102,7 +82,10 @@ export default { showAssetLinksForm() { return this.glFeatures.releaseAssetLinkEditing; }, - isSaveChangesDisabled() { + saveButtonLabel() { + return this.isExistingRelease ? __('Save changes') : __('Create release'); + }, + isFormSubmissionDisabled() { return this.isUpdatingRelease || !this.isValid; }, milestoneComboboxExtraLinks() { @@ -118,53 +101,45 @@ export default { ]; }, }, - created() { - this.fetchRelease(); + mounted() { + // eslint-disable-next-line promise/catch-or-return + this.initializeRelease().then(() => { + // Focus the first non-disabled input element + this.$el.querySelector('input:enabled').focus(); + }); }, methods: { ...mapActions('detail', [ - 'fetchRelease', - 'updateRelease', + 'initializeRelease', + 'saveRelease', 'updateReleaseTitle', 'updateReleaseNotes', 'updateReleaseMilestones', ]), + submitForm() { + if (!this.isFormSubmissionDisabled) { + this.saveRelease(); + } + }, }, }; </script> <template> <div class="d-flex flex-column"> <p class="pt-3 js-subtitle-text" v-html="subtitleText"></p> - <form v-if="showForm" @submit.prevent="updateRelease()"> - <gl-form-group> - <div class="row"> - <div class="col-md-6 col-lg-5 col-xl-4"> - <label for="git-ref">{{ __('Tag name') }}</label> - <gl-form-input - id="git-ref" - v-model="tagName" - type="text" - class="form-control" - aria-describedby="tag-name-help" - disabled - /> - </div> - </div> - <div id="tag-name-help" class="form-text text-muted" v-html="tagNameHintText"></div> - </gl-form-group> + <form v-if="showForm" class="js-quick-submit" @submit.prevent="submitForm"> + <tag-field /> <gl-form-group> <label for="release-title">{{ __('Release title') }}</label> <gl-form-input id="release-title" ref="releaseTitleInput" v-model="releaseTitle" - v-autofocusonshow - autofocus type="text" class="form-control" /> </gl-form-group> - <gl-form-group class="w-50"> + <gl-form-group class="w-50" @keydown.enter.prevent.capture> <label>{{ __('Milestones') }}</label> <div class="d-flex flex-column col-md-6 col-sm-10 pl-0"> <milestone-combobox @@ -182,7 +157,7 @@ export default { :markdown-preview-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" :add-spacing-classes="false" - class="prepend-top-10 append-bottom-10" + class="gl-mt-3 gl-mb-3" > <template #textarea> <textarea @@ -193,8 +168,6 @@ export default { data-supports-quick-actions="false" :aria-label="__('Release notes')" :placeholder="__('Write your release notes or drag your files hereā¦')" - @keydown.meta.enter="updateRelease()" - @keydown.ctrl.enter="updateRelease()" ></textarea> </template> </markdown-field> @@ -209,10 +182,11 @@ export default { category="primary" variant="success" type="submit" - :aria-label="__('Save changes')" - :disabled="isSaveChangesDisabled" - >{{ __('Save changes') }}</gl-button + :disabled="isFormSubmissionDisabled" + data-testid="submit-button" > + {{ saveButtonLabel }} + </gl-button> <gl-button :href="cancelPath" class="js-cancel-button">{{ __('Cancel') }}</gl-button> </div> </form> diff --git a/app/assets/javascripts/releases/components/app_new.vue b/app/assets/javascripts/releases/components/app_new.vue deleted file mode 100644 index 563f76b3281..00000000000 --- a/app/assets/javascripts/releases/components/app_new.vue +++ /dev/null @@ -1,9 +0,0 @@ -<script> -export default { - name: 'ReleaseNewApp', - components: {}, -}; -</script> -<template> - <div></div> -</template> diff --git a/app/assets/javascripts/releases/components/asset_links_form.vue b/app/assets/javascripts/releases/components/asset_links_form.vue index d0d1485d8e7..07fab840067 100644 --- a/app/assets/javascripts/releases/components/asset_links_form.vue +++ b/app/assets/javascripts/releases/components/asset_links_form.vue @@ -49,6 +49,12 @@ export default { this.removeAssetLink(linkId); this.ensureAtLeastOneLink(); }, + updateUrl(link, newUrl) { + this.updateAssetLinkUrl({ linkIdToUpdate: link.id, newUrl }); + }, + updateName(link, newName) { + this.updateAssetLinkName({ linkIdToUpdate: link.id, newName }); + }, hasDuplicateUrl(link) { return Boolean(this.getLinkErrors(link).isDuplicate); }, @@ -138,7 +144,9 @@ export default { type="text" class="form-control" :state="isUrlValid(link)" - @change="updateAssetLinkUrl({ linkIdToUpdate: link.id, newUrl: $event })" + @change="updateUrl(link, $event)" + @keydown.ctrl.enter="updateUrl(link, $event.target.value)" + @keydown.meta.enter="updateUrl(link, $event.target.value)" /> <template #invalid-feedback> <span v-if="hasEmptyUrl(link)" class="invalid-feedback d-inline"> @@ -175,7 +183,9 @@ export default { type="text" class="form-control" :state="isNameValid(link)" - @change="updateAssetLinkName({ linkIdToUpdate: link.id, newName: $event })" + @change="updateName(link, $event)" + @keydown.ctrl.enter="updateName(link, $event.target.value)" + @keydown.meta.enter="updateName(link, $event.target.value)" /> <template #invalid-feedback> <span v-if="hasEmptyName(link)" class="invalid-feedback d-inline"> diff --git a/app/assets/javascripts/releases/components/form_field_container.vue b/app/assets/javascripts/releases/components/form_field_container.vue new file mode 100644 index 00000000000..19e275315a0 --- /dev/null +++ b/app/assets/javascripts/releases/components/form_field_container.vue @@ -0,0 +1,12 @@ +<script> +export default { + name: 'FormFieldContainer', +}; +</script> +<template> + <div class="row"> + <div class="col-md-6 col-lg-5 col-xl-4 gl-display-flex gl-flex-direction-column"> + <slot></slot> + </div> + </div> +</template> diff --git a/app/assets/javascripts/releases/components/release_block_assets.vue b/app/assets/javascripts/releases/components/release_block_assets.vue index ab29ceb0ce6..9583f5737df 100644 --- a/app/assets/javascripts/releases/components/release_block_assets.vue +++ b/app/assets/javascripts/releases/components/release_block_assets.vue @@ -1,10 +1,10 @@ <script> import { GlTooltipDirective, GlLink, GlButton, GlCollapse, GlIcon, GlBadge } from '@gitlab/ui'; +import { difference, get } from 'lodash'; import Icon from '~/vue_shared/components/icon.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { ASSET_LINK_TYPE } from '../constants'; import { __, s__, sprintf } from '~/locale'; -import { difference, get } from 'lodash'; export default { name: 'ReleaseBlockAssets', @@ -138,7 +138,7 @@ export default { :aria-label="$options.externalLinkTooltipText" :title="$options.externalLinkTooltipText" data-testid="external-link-indicator" - class="gl-ml-2 gl-flex-shrink-0 gl-flex-grow-0 gl-text-gray-600" + class="gl-ml-2 gl-flex-shrink-0 gl-flex-grow-0 gl-text-gray-400" /> </gl-link> </li> diff --git a/app/assets/javascripts/releases/components/release_block_author.vue b/app/assets/javascripts/releases/components/release_block_author.vue index 94f2b1795f0..72c578068cd 100644 --- a/app/assets/javascripts/releases/components/release_block_author.vue +++ b/app/assets/javascripts/releases/components/release_block_author.vue @@ -1,6 +1,6 @@ <script> -import { __, sprintf } from '~/locale'; import { GlSprintf } from '@gitlab/ui'; +import { __, sprintf } from '~/locale'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; export default { diff --git a/app/assets/javascripts/releases/components/release_block_milestone_info.vue b/app/assets/javascripts/releases/components/release_block_milestone_info.vue index b16ae400d6b..deff673cc17 100644 --- a/app/assets/javascripts/releases/components/release_block_milestone_info.vue +++ b/app/assets/javascripts/releases/components/release_block_milestone_info.vue @@ -7,9 +7,9 @@ import { GlTooltipDirective, GlSprintf, } from '@gitlab/ui'; +import { sum } from 'lodash'; import { __, n__, sprintf } from '~/locale'; import { MAX_MILESTONES_TO_DISPLAY } from '../constants'; -import { sum } from 'lodash'; export default { name: 'ReleaseBlockMilestoneInfo', diff --git a/app/assets/javascripts/releases/components/tag_field.vue b/app/assets/javascripts/releases/components/tag_field.vue new file mode 100644 index 00000000000..ed8d6e62926 --- /dev/null +++ b/app/assets/javascripts/releases/components/tag_field.vue @@ -0,0 +1,20 @@ +<script> +import { mapGetters } from 'vuex'; +import TagFieldExisting from './tag_field_existing.vue'; +import TagFieldNew from './tag_field_new.vue'; + +export default { + components: { + TagFieldExisting, + TagFieldNew, + }, + computed: { + ...mapGetters('detail', ['isExistingRelease']), + }, +}; +</script> + +<template> + <tag-field-existing v-if="isExistingRelease" /> + <tag-field-new v-else /> +</template> diff --git a/app/assets/javascripts/releases/components/tag_field_existing.vue b/app/assets/javascripts/releases/components/tag_field_existing.vue new file mode 100644 index 00000000000..b84e713df26 --- /dev/null +++ b/app/assets/javascripts/releases/components/tag_field_existing.vue @@ -0,0 +1,51 @@ +<script> +import { mapState } from 'vuex'; +import { uniqueId } from 'lodash'; +import { GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui'; +import FormFieldContainer from './form_field_container.vue'; + +export default { + name: 'TagFieldExisting', + components: { GlFormGroup, GlFormInput, GlSprintf, GlLink, FormFieldContainer }, + computed: { + ...mapState('detail', ['release', 'updateReleaseApiDocsPath']), + inputId() { + return uniqueId('tag-name-input-'); + }, + helpId() { + return uniqueId('tag-name-help-'); + }, + }, +}; +</script> +<template> + <gl-form-group :label="__('Tag name')" :label-for="inputId"> + <form-field-container> + <gl-form-input + :id="inputId" + :value="release.tagName" + type="text" + class="form-control" + :aria-describedby="helpId" + disabled + /> + </form-field-container> + <template #description> + <div :id="helpId" data-testid="tag-name-help"> + <gl-sprintf + :message=" + __( + 'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}', + ) + " + > + <template #link="{ content }"> + <gl-link :href="updateReleaseApiDocsPath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </div> + </template> + </gl-form-group> +</template> diff --git a/app/assets/javascripts/releases/components/tag_field_new.vue b/app/assets/javascripts/releases/components/tag_field_new.vue new file mode 100644 index 00000000000..4779feae886 --- /dev/null +++ b/app/assets/javascripts/releases/components/tag_field_new.vue @@ -0,0 +1,100 @@ +<script> +import { mapState, mapActions, mapGetters } from 'vuex'; +import { GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; +import { __ } from '~/locale'; +import RefSelector from '~/ref/components/ref_selector.vue'; +import FormFieldContainer from './form_field_container.vue'; + +export default { + name: 'TagFieldNew', + components: { GlFormGroup, GlFormInput, RefSelector, FormFieldContainer }, + data() { + return { + // Keeps track of whether or not the user has interacted with + // the input field. This is used to avoid showing validation + // errors immediately when the page loads. + isInputDirty: false, + }; + }, + computed: { + ...mapState('detail', ['projectId', 'release', 'createFrom']), + ...mapGetters('detail', ['validationErrors']), + tagName: { + get() { + return this.release.tagName; + }, + set(tagName) { + this.updateReleaseTagName(tagName); + }, + }, + createFromModel: { + get() { + return this.createFrom; + }, + set(createFrom) { + this.updateCreateFrom(createFrom); + }, + }, + showTagNameValidationError() { + return this.isInputDirty && this.validationErrors.isTagNameEmpty; + }, + tagNameInputId() { + return uniqueId('tag-name-input-'); + }, + createFromSelectorId() { + return uniqueId('create-from-selector-'); + }, + }, + methods: { + ...mapActions('detail', ['updateReleaseTagName', 'updateCreateFrom']), + markInputAsDirty() { + this.isInputDirty = true; + }, + }, + translations: { + noRefSelected: __('No source selected'), + searchPlaceholder: __('Search branches, tags, and commits'), + dropdownHeader: __('Select source'), + }, +}; +</script> +<template> + <div> + <gl-form-group + :label="__('Tag name')" + :label-for="tagNameInputId" + data-testid="tag-name-field" + :state="!showTagNameValidationError" + :invalid-feedback="__('Tag name is required')" + > + <form-field-container> + <gl-form-input + :id="tagNameInputId" + v-model="tagName" + :state="!showTagNameValidationError" + type="text" + class="form-control" + @blur.once="markInputAsDirty" + /> + </form-field-container> + </gl-form-group> + <gl-form-group + :label="__('Create from')" + :label-for="createFromSelectorId" + data-testid="create-from-field" + > + <form-field-container> + <ref-selector + :id="createFromSelectorId" + v-model="createFromModel" + :project-id="projectId" + :translations="$options.translations" + /> + </form-field-container> + <template #description> + {{ __('Existing branch name, tag, or commit SHA') }} + </template> + </gl-form-group> + </div> +</template> |