mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
454 lines
15 KiB
454 lines
15 KiB
2 years ago
|
<template>
|
||
2 years ago
|
<div :class="{ 'pt-10': !hideLabel }">
|
||
2 years ago
|
<v-dialog v-model="dropOrUpload" max-width="600">
|
||
|
<v-card max-width="600">
|
||
2 years ago
|
<v-tabs height="30">
|
||
2 years ago
|
<v-tab>
|
||
2 years ago
|
<v-icon small class="mr-1"> mdi-file-upload-outline </v-icon>
|
||
2 years ago
|
<span class="caption text-capitalize">Upload</span>
|
||
|
</v-tab>
|
||
2 years ago
|
<!-- <v-tab>-->
|
||
|
<!-- <v-icon small class="mr-1">
|
||
2 years ago
|
mdi-link-variant
|
||
|
</v-icon>
|
||
|
<span class="caption text-capitalize">URL</span>
|
||
2 years ago
|
</v-tab>-->
|
||
2 years ago
|
<v-tab>
|
||
2 years ago
|
<v-icon small class="mr-1"> mdi-link-variant </v-icon>
|
||
2 years ago
|
<span class="caption text-capitalize">String</span>
|
||
|
</v-tab>
|
||
2 years ago
|
|
||
2 years ago
|
<v-tab-item>
|
||
2 years ago
|
<div class="nc-json-import-tab-item">
|
||
2 years ago
|
<div
|
||
|
class="nc-droppable d-flex align-center justify-center flex-column"
|
||
|
:style="{
|
||
2 years ago
|
background: dragOver ? '#7772' : '',
|
||
2 years ago
|
}"
|
||
|
@click="$refs.file.click()"
|
||
|
@drop.prevent="dropHandler"
|
||
|
@dragover.prevent="dragOver = true"
|
||
|
@dragenter.prevent="dragOver = true"
|
||
|
@dragexit="dragOver = false"
|
||
|
@dragleave="dragOver = false"
|
||
|
@dragend="dragOver = false"
|
||
|
>
|
||
2 years ago
|
<x-icon :color="['primary', 'grey']" size="50"> mdi-file-plus-outline </x-icon>
|
||
|
<p class="title mb-1 mt-2">
|
||
2 years ago
|
<!-- Select File to Upload-->
|
||
|
{{ $t('msg.info.upload') }}
|
||
|
</p>
|
||
|
<p class="grey--text mb-1">
|
||
|
<!-- or drag and drop file-->
|
||
|
{{ $t('msg.info.upload_sub') }}
|
||
|
</p>
|
||
2 years ago
|
|
||
2 years ago
|
<p v-if="quickImportType == 'excel'" class="caption grey--text">
|
||
|
<!-- Supported: .xls, .xlsx, .xlsm, .ods, .ots -->
|
||
|
{{ $t('msg.info.excelSupport') }}
|
||
|
</p>
|
||
2 years ago
|
</div>
|
||
2 years ago
|
</div>
|
||
|
</v-tab-item>
|
||
|
<!-- <v-tab-item>
|
||
2 years ago
|
<div class="nc-json-import-tab-item align-center">
|
||
2 years ago
|
<div class="pa-4 d-100 h-100">
|
||
|
<v-form ref="form" v-model="valid">
|
||
|
<div class="d-flex">
|
||
2 years ago
|
<!– todo: i18n label–>
|
||
2 years ago
|
<v-text-field
|
||
|
v-model="url"
|
||
|
hide-details="auto"
|
||
|
type="url"
|
||
2 years ago
|
label="Enter JSON file url"
|
||
2 years ago
|
class="caption"
|
||
|
outlined
|
||
|
dense
|
||
2 years ago
|
:rules="
|
||
|
[
|
||
|
v => !!v || $t('general.required'),
|
||
|
v => !(/(10)(\.([2]([0-5][0-5]|[01234][6-9])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}|(172)\.(1[6-9]|2[0-9]|3[0-1])(\.(2[0-4][0-9]|25[0-5]|[1][0-9][0-9]|[1-9][0-9]|[0-9])){2}|(192)\.(168)(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){2}|(0.0.0.0)|localhost?/g).test(v) || errorMessages.ipBlockList
|
||
|
]"
|
||
2 years ago
|
/>
|
||
2 years ago
|
<v-btn v-t="['c:project:create:json:load-url']" class="ml-3" color="primary" @click="loadUrl">
|
||
2 years ago
|
<!–Load–>
|
||
2 years ago
|
{{ $t('general.load') }}
|
||
|
</v-btn>
|
||
|
</div>
|
||
|
</v-form>
|
||
|
</div>
|
||
|
</div>
|
||
2 years ago
|
</v-tab-item>-->
|
||
2 years ago
|
<v-tab-item>
|
||
|
<div class="nc-json-import-tab-item align-center">
|
||
|
<div class="pa-4 d-100 h-100">
|
||
|
<v-form ref="form" v-model="valid">
|
||
|
<div class="nc-json-editor-wrapper">
|
||
2 years ago
|
<v-btn small class="nc-json-format-btn" @click="formatJson"> Format </v-btn>
|
||
2 years ago
|
|
||
2 years ago
|
<!--label="Enter excel file url"-->
|
||
2 years ago
|
<monaco-json-editor ref="editor" v-model="jsonString" style="height: 320px" />
|
||
2 years ago
|
<div class="text-center mt-4">
|
||
2 years ago
|
<v-btn
|
||
|
v-t="['c:project:create:excel:load-url']"
|
||
|
class="ml-3"
|
||
|
color="primary"
|
||
|
@click="loadJsonString"
|
||
|
>
|
||
2 years ago
|
<!--Load-->
|
||
|
{{ $t('general.load') }}
|
||
|
</v-btn>
|
||
2 years ago
|
</div>
|
||
2 years ago
|
</div>
|
||
|
</v-form>
|
||
2 years ago
|
</div>
|
||
2 years ago
|
</div>
|
||
|
</v-tab-item>
|
||
2 years ago
|
</v-tabs>
|
||
|
|
||
|
<div class="px-4 pb-2">
|
||
|
<div class="d-flex">
|
||
|
<v-spacer />
|
||
|
<span class="caption pointer grey--text" @click="showMore = !showMore">
|
||
|
{{ showMore ? $t('general.hideAll') : $t('general.showMore') }}
|
||
|
<v-icon small color="grey lighten-1">mdi-menu-{{ showMore ? 'up' : 'down' }}</v-icon>
|
||
|
</span>
|
||
|
</div>
|
||
2 years ago
|
<div class="mb-2 pt-2 nc-json-import-options" :style="{ maxHeight: showMore ? '200px' : '0' }">
|
||
2 years ago
|
<p />
|
||
|
<!--hint="# of rows to parse to infer data type"-->
|
||
|
<v-text-field
|
||
|
v-model="parserConfig.maxRowsToParse"
|
||
|
style="max-width: 250px"
|
||
|
class="caption mx-auto"
|
||
|
dense
|
||
|
persistent-hint
|
||
|
:hint="$t('msg.info.footMsg')"
|
||
|
outlined
|
||
|
type="number"
|
||
|
/>
|
||
2 years ago
|
|
||
|
<v-checkbox
|
||
|
v-model="parserConfig.normalizeNested"
|
||
|
style="width: 250px"
|
||
|
class="mx-auto mb-2"
|
||
|
dense
|
||
|
hide-details
|
||
|
>
|
||
|
<template #label>
|
||
2 years ago
|
<span class="caption">Flatten nested</span>
|
||
|
<v-tooltip bottom position-y="">
|
||
|
<template #activator="{ on }">
|
||
2 years ago
|
<v-icon small class="ml-1" v-on="on"> mdi-information-outline </v-icon>
|
||
2 years ago
|
</template>
|
||
|
<div class="caption" style="width: 260px">
|
||
2 years ago
|
If flatten nested option is set it will flatten nested object as root level property. In normal case
|
||
|
nested object will treat as JSON column.
|
||
|
<br />
|
||
|
<br />
|
||
|
For example the following input:
|
||
|
<code class="caption font-weight-bold"
|
||
|
>{ "prop1": { "prop2": "value" }, "prop3": "value", "prop4": 1 }</code
|
||
|
>
|
||
|
will treat as:
|
||
|
<code class="caption font-weight-bold"
|
||
|
>{ "prop1_prop2": "value", "prop3": "value", "prop4": 1 }</code
|
||
|
>
|
||
2 years ago
|
</div>
|
||
|
</v-tooltip>
|
||
2 years ago
|
</template>
|
||
|
</v-checkbox>
|
||
2 years ago
|
<v-checkbox v-model="parserConfig.importData" style="width: 250px" class="mx-auto mb-2" dense hide-details>
|
||
2 years ago
|
<template #label>
|
||
|
<span class="caption">Import data</span>
|
||
|
</template>
|
||
|
</v-checkbox>
|
||
2 years ago
|
</div>
|
||
|
</div>
|
||
|
</v-card>
|
||
|
</v-dialog>
|
||
|
|
||
|
<v-tooltip bottom>
|
||
2 years ago
|
<template #activator="{ on }">
|
||
2 years ago
|
<input
|
||
|
ref="file"
|
||
2 years ago
|
class="nc-json-import-input"
|
||
2 years ago
|
type="file"
|
||
|
style="display: none"
|
||
2 years ago
|
accept=".json"
|
||
2 years ago
|
@change="_change($event)"
|
||
2 years ago
|
/>
|
||
|
<v-btn v-if="!hideLabel" small outlined v-on="on" @click="$refs.file.click()">
|
||
|
<v-icon small class="mr-1"> mdi-file-excel-outline </v-icon>
|
||
2 years ago
|
<!--Import-->
|
||
|
{{ $t('activity.import') }}
|
||
|
</v-btn>
|
||
|
</template>
|
||
2 years ago
|
<span class="caption">Create template from JSON</span>
|
||
2 years ago
|
</v-tooltip>
|
||
|
|
||
|
<v-dialog v-if="templateData" v-model="templateEditorModal" max-width="1000">
|
||
|
<v-card class="pa-6" min-width="500">
|
||
2 years ago
|
<template-editor :project-template.sync="templateData" json-import :quick-import-type="quickImportType">
|
||
2 years ago
|
<template #toolbar="{ valid }">
|
||
2 years ago
|
<h3 class="mt-2 grey--text">
|
||
2 years ago
|
<span> JSON Import </span>
|
||
2 years ago
|
</h3>
|
||
|
<v-spacer />
|
||
|
<v-spacer />
|
||
|
<create-project-from-template-btn
|
||
|
:template-data="templateData"
|
||
|
:import-data="importData"
|
||
|
:import-to-project="importToProject"
|
||
2 years ago
|
json-import
|
||
2 years ago
|
:valid="valid"
|
||
|
create-gql-text="Import as GQL Project"
|
||
|
create-rest-text="Import as REST Project"
|
||
2 years ago
|
@closeModal="$emit('closeModal'), (templateEditorModal = false)"
|
||
2 years ago
|
>
|
||
|
<!--Import Excel-->
|
||
|
<span v-if="quickImportType === 'excel'">
|
||
|
{{ $t('activity.importExcel') }}
|
||
|
</span>
|
||
|
<!--Import CSV-->
|
||
|
<span v-if="quickImportType === 'csv'">
|
||
|
{{ $t('activity.importCSV') }}
|
||
|
</span>
|
||
|
</create-project-from-template-btn>
|
||
|
</template>
|
||
|
</template-editor>
|
||
|
</v-card>
|
||
|
</v-dialog>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
2 years ago
|
import TemplateEditor from '~/components/templates/Editor';
|
||
|
import CreateProjectFromTemplateBtn from '~/components/templates/CreateProjectFromTemplateBtn';
|
||
|
import MonacoJsonEditor from '~/components/monaco/MonacoJsonEditor';
|
||
|
import JSONTemplateAdapter from '~/components/import/templateParsers/JSONTemplateAdapter';
|
||
|
import JSONUrlTemplateAdapter from '~/components/import/templateParsers/JSONUrlTemplateAdapter';
|
||
2 years ago
|
|
||
|
export default {
|
||
|
name: 'JsonImport',
|
||
|
components: { MonacoJsonEditor, CreateProjectFromTemplateBtn, TemplateEditor },
|
||
|
props: {
|
||
|
hideLabel: Boolean,
|
||
|
value: Boolean,
|
||
|
importToProject: Boolean,
|
||
2 years ago
|
quickImportType: String,
|
||
2 years ago
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
templateEditorModal: false,
|
||
|
valid: null,
|
||
|
templateData: null,
|
||
|
importData: null,
|
||
|
dragOver: false,
|
||
|
url: '',
|
||
|
showMore: false,
|
||
|
parserConfig: {
|
||
2 years ago
|
maxRowsToParse: 500,
|
||
2 years ago
|
normalizeNested: true,
|
||
2 years ago
|
importData: true,
|
||
2 years ago
|
},
|
||
|
filename: '',
|
||
2 years ago
|
jsonString: '',
|
||
|
errorMessages: {
|
||
2 years ago
|
ipBlockList: 'IP Not allowed!',
|
||
2 years ago
|
importJSON: 'Target file is not an accepted file type. The accepted file type is .json!',
|
||
|
},
|
||
|
};
|
||
2 years ago
|
},
|
||
|
computed: {
|
||
|
dropOrUpload: {
|
||
|
set(v) {
|
||
2 years ago
|
this.$emit('input', v);
|
||
2 years ago
|
},
|
||
|
get() {
|
||
2 years ago
|
return this.value;
|
||
|
},
|
||
2 years ago
|
},
|
||
|
tables() {
|
||
2 years ago
|
return this.$store.state.project.tables || [];
|
||
|
},
|
||
2 years ago
|
},
|
||
|
mounted() {
|
||
|
if (this.$route && this.$route.query && this.$route.query.excelUrl) {
|
||
2 years ago
|
this.url = this.$route.query.excelUrl;
|
||
|
this.loadUrl();
|
||
2 years ago
|
}
|
||
|
},
|
||
|
methods: {
|
||
2 years ago
|
formatJson() {
|
||
2 years ago
|
console.log(this.$refs.editor);
|
||
|
this.$refs.editor.format();
|
||
2 years ago
|
},
|
||
2 years ago
|
|
||
|
selectFile() {
|
||
2 years ago
|
this.$refs.file.files = null;
|
||
|
this.$refs.file.click();
|
||
2 years ago
|
},
|
||
|
|
||
|
_change(event) {
|
||
2 years ago
|
const files = event.target.files;
|
||
2 years ago
|
if (files && files[0]) {
|
||
2 years ago
|
this._file(files[0]);
|
||
|
event.target.value = '';
|
||
2 years ago
|
}
|
||
|
},
|
||
|
async _file(file) {
|
||
2 years ago
|
this.templateData = null;
|
||
|
this.importData = null;
|
||
|
this.$store.commit('loader/MutMessage', 'Loading excel file');
|
||
|
let i = 0;
|
||
2 years ago
|
const int = setInterval(() => {
|
||
2 years ago
|
this.$store.commit('loader/MutMessage', `Loading excel file${'.'.repeat(++i % 4)}`);
|
||
|
}, 1000);
|
||
|
this.dropOrUpload = false;
|
||
|
const reader = new FileReader();
|
||
|
this.filename = file.name;
|
||
|
|
||
|
reader.onload = async e => {
|
||
|
const ab = e.target.result;
|
||
|
await this.parseAndExtractData('file', ab, file.name);
|
||
|
this.$store.commit('loader/MutMessage', null);
|
||
|
|
||
|
clearInterval(int);
|
||
|
};
|
||
|
|
||
|
const handleEvent = event => {
|
||
|
this.$store.commit('loader/MutMessage', `${event.type}: ${event.loaded} bytes transferred`);
|
||
|
};
|
||
|
|
||
|
reader.addEventListener('progress', handleEvent);
|
||
|
reader.onerror = e => {
|
||
|
console.log('error', e);
|
||
|
this.$store.commit('loader/MutClear');
|
||
|
};
|
||
|
reader.readAsText(file);
|
||
2 years ago
|
},
|
||
|
|
||
|
async parseAndExtractData(type, val, name) {
|
||
|
try {
|
||
2 years ago
|
let templateGenerator;
|
||
|
this.templateData = null;
|
||
|
this.importData = null;
|
||
2 years ago
|
switch (type) {
|
||
|
case 'file':
|
||
2 years ago
|
templateGenerator = new JSONTemplateAdapter(name, val, this.parserConfig);
|
||
|
break;
|
||
2 years ago
|
case 'url':
|
||
2 years ago
|
templateGenerator = new JSONUrlTemplateAdapter(val, this.$store, this.parserConfig, this.$api);
|
||
|
break;
|
||
2 years ago
|
case 'string':
|
||
2 years ago
|
templateGenerator = new JSONTemplateAdapter(name, val, this.parserConfig);
|
||
|
break;
|
||
2 years ago
|
}
|
||
2 years ago
|
await templateGenerator.init();
|
||
|
templateGenerator.parse();
|
||
|
this.templateData = templateGenerator.getTemplate();
|
||
2 years ago
|
|
||
2 years ago
|
this.templateData.tables[0].table_name = this.populateUniqueTableName();
|
||
2 years ago
|
|
||
2 years ago
|
this.importData = templateGenerator.getData();
|
||
|
this.templateEditorModal = true;
|
||
2 years ago
|
} catch (e) {
|
||
2 years ago
|
console.log(e);
|
||
|
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000);
|
||
2 years ago
|
}
|
||
|
},
|
||
|
|
||
|
dropHandler(ev) {
|
||
2 years ago
|
this.dragOver = false;
|
||
|
let file;
|
||
2 years ago
|
if (ev.dataTransfer.items) {
|
||
|
// Use DataTransferItemList interface to access the file(s)
|
||
|
if (ev.dataTransfer.items.length && ev.dataTransfer.items[0].kind === 'file') {
|
||
2 years ago
|
file = ev.dataTransfer.items[0].getAsFile();
|
||
2 years ago
|
}
|
||
|
} else if (ev.dataTransfer.files.length) {
|
||
2 years ago
|
file = ev.dataTransfer.files[0];
|
||
2 years ago
|
}
|
||
|
|
||
|
if (!file) {
|
||
2 years ago
|
return;
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
if (!/.*\.json/.test(file.name)) {
|
||
2 years ago
|
return this.$toast.error(this.errorMessages.importJSON).goAway(3000);
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
this._file(file);
|
||
2 years ago
|
},
|
||
|
dragOverHandler(ev) {
|
||
|
// Prevent default behavior (Prevent file from being opened)
|
||
2 years ago
|
ev.preventDefault();
|
||
2 years ago
|
},
|
||
2 years ago
|
populateUniqueTableName() {
|
||
2 years ago
|
let c = 1;
|
||
|
while (this.tables.some(t => t.title === `Sheet${c}`)) {
|
||
|
c++;
|
||
|
}
|
||
|
return `Sheet${c}`;
|
||
2 years ago
|
},
|
||
2 years ago
|
async loadUrl() {
|
||
|
if ((this.$refs.form && !this.$refs.form.validate()) || !this.url) {
|
||
2 years ago
|
return;
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
this.$store.commit('loader/MutMessage', 'Loading json file from url');
|
||
2 years ago
|
|
||
2 years ago
|
let i = 0;
|
||
2 years ago
|
const int = setInterval(() => {
|
||
2 years ago
|
this.$store.commit('loader/MutMessage', `Loading json file${'.'.repeat(++i % 4)}`);
|
||
|
}, 1000);
|
||
2 years ago
|
|
||
2 years ago
|
this.dropOrUpload = false;
|
||
2 years ago
|
|
||
2 years ago
|
await this.parseAndExtractData('url', this.url, '');
|
||
|
clearInterval(int);
|
||
|
this.$store.commit('loader/MutClear');
|
||
2 years ago
|
},
|
||
|
|
||
|
async loadJsonString() {
|
||
2 years ago
|
await this.parseAndExtractData('string', this.jsonString);
|
||
|
this.$store.commit('loader/MutClear');
|
||
|
},
|
||
|
},
|
||
|
};
|
||
2 years ago
|
</script>
|
||
|
|
||
|
<style scoped>
|
||
|
.nc-droppable {
|
||
|
width: 100%;
|
||
|
min-height: 200px;
|
||
|
border-radius: 4px;
|
||
|
border: 2px dashed #ddd;
|
||
|
}
|
||
|
|
||
2 years ago
|
.nc-json-import-tab-item {
|
||
2 years ago
|
min-height: 400px;
|
||
|
padding: 20px;
|
||
|
display: flex;
|
||
|
align-items: stretch;
|
||
|
width: 100%;
|
||
|
}
|
||
|
|
||
2 years ago
|
.nc-json-import-options {
|
||
2 years ago
|
transition: 0.4s max-height;
|
||
2 years ago
|
overflow: hidden;
|
||
|
}
|
||
2 years ago
|
|
||
2 years ago
|
.nc-json-editor-wrapper {
|
||
2 years ago
|
position: relative;
|
||
|
}
|
||
|
|
||
2 years ago
|
.nc-json-format-btn {
|
||
|
position: absolute;
|
||
|
right: 4px;
|
||
|
top: 4px;
|
||
|
z-index: 9;
|
||
2 years ago
|
}
|
||
2 years ago
|
</style>
|