gitlab-org--gitlab-foss/app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue

164 lines
3.9 KiB
Vue

<script>
import { GlButton, GlFormGroup } from '@gitlab/ui';
import { cloneDeep } from 'lodash';
import { s__, sprintf } from '~/locale';
import { SNIPPET_MAX_BLOBS } from '../constants';
import { createBlob, decorateBlob, diffAll } from '../utils/blob';
import SnippetBlobEdit from './snippet_blob_edit.vue';
export default {
components: {
SnippetBlobEdit,
GlButton,
GlFormGroup,
},
props: {
initBlobs: {
type: Array,
required: true,
},
isValid: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
// This is a dictionary (by .id) of the original blobs and
// is used as the baseline for calculating diffs
// (e.g., what has been deleted, changed, renamed, etc.)
blobsOrig: {},
// This is a dictionary (by .id) of the current blobs and
// is updated as the user makes changes.
blobs: {},
// This is a list of blob ID's in order how they should be
// presented.
blobIds: [],
};
},
computed: {
actions() {
return diffAll(this.blobs, this.blobsOrig);
},
count() {
return this.blobIds.length;
},
addLabel() {
return sprintf(s__('Snippets|Add another file %{num}/%{total}'), {
num: this.count,
total: SNIPPET_MAX_BLOBS,
});
},
canDelete() {
return this.count > 1;
},
canAdd() {
return this.count < SNIPPET_MAX_BLOBS;
},
firstInputId() {
const blobId = this.blobIds[0];
if (!blobId) {
return '';
}
return `${blobId}_file_path`;
},
},
watch: {
actions: {
immediate: true,
handler(val) {
this.$emit('actions', val);
},
},
},
created() {
const blobs = this.initBlobs.map(decorateBlob);
const blobsById = blobs.reduce((acc, x) => Object.assign(acc, { [x.id]: x }), {});
this.blobsOrig = blobsById;
this.blobs = cloneDeep(blobsById);
this.blobIds = blobs.map((x) => x.id);
// Show 1 empty blob if none exist
if (!this.blobIds.length) {
this.addBlob();
}
},
methods: {
updateBlobContent(id, content) {
const origBlob = this.blobsOrig[id];
const blob = this.blobs[id];
blob.content = content;
// If we've received content, but we haven't loaded the content before
// then this is also the original content.
if (origBlob && !origBlob.isLoaded) {
blob.isLoaded = true;
origBlob.isLoaded = true;
origBlob.content = content;
}
},
updateBlobFilePath(id, path) {
const blob = this.blobs[id];
blob.path = path;
},
addBlob() {
const blob = createBlob();
this.$set(this.blobs, blob.id, blob);
this.blobIds.push(blob.id);
},
deleteBlob(id) {
this.blobIds = this.blobIds.filter((x) => x !== id);
this.$delete(this.blobs, id);
},
updateBlob(id, args) {
if ('content' in args) {
this.updateBlobContent(id, args.content);
}
if ('path' in args) {
this.updateBlobFilePath(id, args.path);
}
},
},
};
</script>
<template>
<div class="form-group">
<gl-form-group
:label="s__('Snippets|Files')"
:label-for="firstInputId"
:invalid-feedback="
s__(
'Snippets|Snippets can\'t contain empty files. Ensure all files have content, or delete them.',
)
"
:state="isValid"
>
<snippet-blob-edit
v-for="(blobId, index) in blobIds"
:key="blobId"
:class="{ 'gl-mt-3': index > 0 }"
:blob="blobs[blobId]"
:can-delete="canDelete"
@blob-updated="updateBlob(blobId, $event)"
@delete="deleteBlob(blobId)"
/>
</gl-form-group>
<gl-button
:disabled="!canAdd"
data-testid="add_button"
class="gl-my-3"
variant="dashed"
data-qa-selector="add_file_button"
@click="addBlob"
>{{ addLabel }}</gl-button
>
</div>
</template>