1<script> 2import { GlButton } from '@gitlab/ui'; 3import { cloneDeep } from 'lodash'; 4import { s__, sprintf } from '~/locale'; 5import { SNIPPET_MAX_BLOBS } from '../constants'; 6import { createBlob, decorateBlob, diffAll } from '../utils/blob'; 7import SnippetBlobEdit from './snippet_blob_edit.vue'; 8 9export default { 10 components: { 11 SnippetBlobEdit, 12 GlButton, 13 }, 14 props: { 15 initBlobs: { 16 type: Array, 17 required: true, 18 }, 19 }, 20 data() { 21 return { 22 // This is a dictionary (by .id) of the original blobs and 23 // is used as the baseline for calculating diffs 24 // (e.g., what has been deleted, changed, renamed, etc.) 25 blobsOrig: {}, 26 // This is a dictionary (by .id) of the current blobs and 27 // is updated as the user makes changes. 28 blobs: {}, 29 // This is a list of blob ID's in order how they should be 30 // presented. 31 blobIds: [], 32 }; 33 }, 34 computed: { 35 actions() { 36 return diffAll(this.blobs, this.blobsOrig); 37 }, 38 count() { 39 return this.blobIds.length; 40 }, 41 addLabel() { 42 return sprintf(s__('Snippets|Add another file %{num}/%{total}'), { 43 num: this.count, 44 total: SNIPPET_MAX_BLOBS, 45 }); 46 }, 47 canDelete() { 48 return this.count > 1; 49 }, 50 canAdd() { 51 return this.count < SNIPPET_MAX_BLOBS; 52 }, 53 firstInputId() { 54 const blobId = this.blobIds[0]; 55 56 if (!blobId) { 57 return ''; 58 } 59 60 return `${blobId}_file_path`; 61 }, 62 }, 63 watch: { 64 actions: { 65 immediate: true, 66 handler(val) { 67 this.$emit('actions', val); 68 }, 69 }, 70 }, 71 created() { 72 const blobs = this.initBlobs.map(decorateBlob); 73 const blobsById = blobs.reduce((acc, x) => Object.assign(acc, { [x.id]: x }), {}); 74 75 this.blobsOrig = blobsById; 76 this.blobs = cloneDeep(blobsById); 77 this.blobIds = blobs.map((x) => x.id); 78 79 // Show 1 empty blob if none exist 80 if (!this.blobIds.length) { 81 this.addBlob(); 82 } 83 }, 84 methods: { 85 updateBlobContent(id, content) { 86 const origBlob = this.blobsOrig[id]; 87 const blob = this.blobs[id]; 88 89 blob.content = content; 90 91 // If we've received content, but we haven't loaded the content before 92 // then this is also the original content. 93 if (origBlob && !origBlob.isLoaded) { 94 blob.isLoaded = true; 95 origBlob.isLoaded = true; 96 origBlob.content = content; 97 } 98 }, 99 updateBlobFilePath(id, path) { 100 const blob = this.blobs[id]; 101 102 blob.path = path; 103 }, 104 addBlob() { 105 const blob = createBlob(); 106 107 this.$set(this.blobs, blob.id, blob); 108 this.blobIds.push(blob.id); 109 }, 110 deleteBlob(id) { 111 this.blobIds = this.blobIds.filter((x) => x !== id); 112 this.$delete(this.blobs, id); 113 }, 114 updateBlob(id, args) { 115 if ('content' in args) { 116 this.updateBlobContent(id, args.content); 117 } 118 if ('path' in args) { 119 this.updateBlobFilePath(id, args.path); 120 } 121 }, 122 }, 123}; 124</script> 125<template> 126 <div class="form-group"> 127 <label :for="firstInputId">{{ s__('Snippets|Files') }}</label> 128 <snippet-blob-edit 129 v-for="(blobId, index) in blobIds" 130 :key="blobId" 131 :class="{ 'gl-mt-3': index > 0 }" 132 :blob="blobs[blobId]" 133 :can-delete="canDelete" 134 @blob-updated="updateBlob(blobId, $event)" 135 @delete="deleteBlob(blobId)" 136 /> 137 <gl-button 138 :disabled="!canAdd" 139 data-testid="add_button" 140 class="gl-my-3" 141 variant="dashed" 142 data-qa-selector="add_file_button" 143 @click="addBlob" 144 >{{ addLabel }}</gl-button 145 > 146 </div> 147</template> 148