1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-26 05:37:13 +00:00
bramw_baserow/web-frontend/modules/database/components/table/TableCSVImporter.vue
2021-05-18 11:12:07 +00:00

214 lines
7 KiB
Vue

<template>
<div>
<div class="control">
<label class="control__label">Choose CSV file</label>
<div class="control__description">
You can import an existing CSV by uploading the .CSV file with tabular
data. Most spreadsheet applications will allow you to export your
spreadsheet as a .CSV file.
</div>
<div class="control__elements">
<div class="file-upload">
<input
v-show="false"
ref="file"
type="file"
accept=".csv"
@change="select($event)"
/>
<a
class="button button--large button--ghost file-upload__button"
@click.prevent="$refs.file.click($event)"
>
<i class="fas fa-cloud-upload-alt"></i>
Choose CSV file
</a>
<div class="file-upload__file">{{ filename }}</div>
</div>
<div v-if="$v.filename.$error" class="error">
This field is required.
</div>
</div>
</div>
<div v-if="filename !== ''" class="row">
<div class="col col-4">
<div class="control">
<label class="control__label">Column separator</label>
<div class="control__elements">
<Dropdown v-model="columnSeparator" @input="reload()">
<DropdownItem name="auto detect" value="auto"></DropdownItem>
<DropdownItem name="," value=","></DropdownItem>
<DropdownItem name=";" value=";"></DropdownItem>
<DropdownItem name="|" value="|"></DropdownItem>
<DropdownItem name="<tab>" value="\t"></DropdownItem>
<DropdownItem
name="record separator (30)"
:value="String.fromCharCode(30)"
></DropdownItem>
<DropdownItem
name="unit separator (31)"
:value="String.fromCharCode(31)"
></DropdownItem>
</Dropdown>
</div>
</div>
</div>
<div class="col col-8">
<div class="control">
<label class="control__label">Encoding</label>
<div class="control__elements">
<CharsetDropdown
v-model="encoding"
@input="reload()"
></CharsetDropdown>
</div>
</div>
</div>
</div>
<div v-if="filename !== ''" class="row">
<div class="col col-6">
<div class="control">
<label class="control__label">First row is header</label>
<div class="control__elements">
<Checkbox v-model="values.firstRowHeader" @input="reload()"
>yes</Checkbox
>
</div>
</div>
</div>
</div>
<div
v-if="error !== ''"
class="alert alert--error alert--has-icon margin-top-1"
>
<div class="alert__icon">
<i class="fas fa-exclamation"></i>
</div>
<div class="alert__title">Something went wrong</div>
<p class="alert__content">
{{ error }}
</p>
</div>
<TableImporterPreview
v-if="error === '' && Object.keys(preview).length !== 0"
:preview="preview"
></TableImporterPreview>
</div>
</template>
<script>
import Papa from 'papaparse'
import { required } from 'vuelidate/lib/validators'
import form from '@baserow/modules/core/mixins/form'
import CharsetDropdown from '@baserow/modules/core/components/helpers/CharsetDropdown'
import importer from '@baserow/modules/database/mixins/importer'
import TableImporterPreview from '@baserow/modules/database/components/table/TableImporterPreview'
export default {
name: 'TableCSVImporter',
components: { TableImporterPreview, CharsetDropdown },
mixins: [form, importer],
data() {
return {
values: {
data: '',
firstRowHeader: true,
},
filename: '',
columnSeparator: 'auto',
encoding: 'utf-8',
error: '',
rawData: null,
preview: {},
}
},
validations: {
values: {
data: { required },
},
filename: { required },
},
methods: {
/**
* Method that is called when a file has been chosen. It will check if the file is
* not larger than 15MB. Otherwise it will take a long time and possibly a crash
* if so many entries have to be loaded into memory. If the file is valid, the
* contents will be loaded into memory and the reload method will be called which
* parses the content.
*/
select(event) {
if (event.target.files.length === 0) {
return
}
const file = event.target.files[0]
const maxSize = 1024 * 1024 * 15
if (file.size > maxSize) {
this.filename = ''
this.values.data = ''
this.error = 'The maximum file size is 15MB.'
this.preview = {}
this.$emit('input', this.value)
} else {
this.filename = file.name
const reader = new FileReader()
reader.addEventListener('load', (event) => {
this.rawData = event.target.result
this.reload()
})
reader.readAsArrayBuffer(event.target.files[0])
}
},
/**
* Parses the raw data with the user configured delimiter. If all looks good the
* data is stored as a string because all the entries don't have to be reactive.
* Also a small preview will be generated. If something goes wrong, for example
* when the CSV doesn't have any entries the appropriate error will be shown.
*/
reload() {
const decoder = new TextDecoder(this.encoding)
const decodedData = decoder.decode(this.rawData)
const limit = this.$env.INITIAL_TABLE_DATA_LIMIT
const count = decodedData.split(/\r\n|\r|\n/).length
if (limit !== null && count > limit) {
this.values.data = ''
this.error = `It is not possible to import more than ${limit} rows.`
this.preview = {}
return
}
Papa.parse(decodedData, {
delimiter: this.columnSeparator === 'auto' ? '' : this.columnSeparator,
complete: (data) => {
if (data.data.length === 0) {
// We need at least a single entry otherwise the user has probably chosen
// a wrong file.
this.values.data = ''
this.error = 'This CSV file is empty.'
this.preview = {}
} else {
// If parsed successfully and it is not empty then the initial data can be
// prepared for creating the table. We store the data stringified because
// it doesn't need to be reactive.
this.values.data = JSON.stringify(data.data)
this.error = ''
this.preview = this.getPreview(
data.data,
this.values.firstRowHeader
)
}
},
error(error) {
// Papa parse has resulted in an error which we need to display to the user.
// All previously loaded data will be removed.
this.values.data = ''
this.error = error.errors[0].message
this.preview = {}
},
})
},
},
}
</script>