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