mirror of https://github.com/nocodb/nocodb
Pranav C
3 years ago
12 changed files with 537 additions and 119 deletions
@ -0,0 +1,90 @@
|
||||
import XLSX from 'xlsx' |
||||
import TemplateGenerator from '~/components/import/TemplateGenerator' |
||||
import { UITypes } from '~/components/project/spreadsheet/helpers/uiTypes' |
||||
|
||||
const excelTypeToUidt = { |
||||
d: UITypes.DateTime, |
||||
b: UITypes.Checkbox, |
||||
n: UITypes.Number, |
||||
s: UITypes.SingleLineText |
||||
} |
||||
|
||||
export default class ExcelTemplateAdapter extends TemplateGenerator { |
||||
constructor(name, ab) { |
||||
super() |
||||
this.name = name |
||||
this.wb = XLSX.read(new Uint8Array(ab), { type: 'array' }) |
||||
this.project = { |
||||
title: this.name, |
||||
tables: [] |
||||
} |
||||
this.data = {} |
||||
} |
||||
|
||||
parse() { |
||||
for (let i = 0; i < this.wb.SheetNames.length; i++) { |
||||
const sheet = this.wb.SheetNames[i] |
||||
const table = { tn: sheet, columns: [] } |
||||
this.data[sheet] = [] |
||||
const ws = this.wb.Sheets[sheet] |
||||
const rows = XLSX.utils.sheet_to_json(ws, { header: 1 }) |
||||
|
||||
for (let col = 0; col < rows[0].length; col++) { |
||||
const column = { |
||||
cn: rows[0][col] |
||||
} |
||||
|
||||
const cellProps = ws[`${col.toString(26).split('').map(s => (parseInt(s, 26) + 10).toString(36).toUpperCase())}2`] |
||||
|
||||
column.uidt = excelTypeToUidt[cellProps.t] || UITypes.SingleLineText |
||||
|
||||
// todo: optimize
|
||||
if (column.uidt === UITypes.SingleLineText) { |
||||
// check for long text
|
||||
if (rows.some(r => (r[col] || '').toString().length > 255)) { |
||||
column.uidt = UITypes.LongText |
||||
} else { |
||||
const vals = rows.slice(1).map(r => r[col]).filter(v => v !== null && v !== undefined) |
||||
|
||||
// check column is multi or single select by comparing unique values
|
||||
if (vals.some(v => v && v.includes(','))) { |
||||
const flattenedVals = vals.flatMap(v => v ? v.split(',') : []) |
||||
const uniqueVals = new Set(flattenedVals) |
||||
if (flattenedVals.length > uniqueVals.size && uniqueVals.size <= flattenedVals.length / 10) { |
||||
column.uidt = UITypes.MultiSelect |
||||
column.dtxp = [...uniqueVals].join(',') |
||||
} |
||||
} else { |
||||
const uniqueVals = new Set(vals) |
||||
if (vals.length > uniqueVals.size && uniqueVals.size <= vals.length / 10) { |
||||
column.uidt = UITypes.SingleSelect |
||||
column.dtxp = [...uniqueVals].join(',') |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
table.columns.push(column) |
||||
} |
||||
|
||||
for (const row of rows.slice(1)) { |
||||
const rowData = {} |
||||
for (let i = 0; i < table.columns.length; i++) { |
||||
// toto: do parsing if necessary based on type
|
||||
rowData[table.columns[i].cn] = row[i] |
||||
} |
||||
this.data[sheet].push(rowData) |
||||
} |
||||
|
||||
this.project.tables.push(table) |
||||
} |
||||
} |
||||
|
||||
getTemplate() { |
||||
return this.project |
||||
} |
||||
|
||||
getData() { |
||||
return this.data |
||||
} |
||||
} |
@ -0,0 +1,13 @@
|
||||
export default class TemplateGenerator { |
||||
parse() { |
||||
throw new Error('\'parse\' method is not implemented') |
||||
} |
||||
|
||||
getTemplate() { |
||||
throw new Error('\'getTemplate\' method is not implemented') |
||||
} |
||||
|
||||
getData() { |
||||
throw new Error('\'getData\' method is not implemented') |
||||
} |
||||
} |
@ -0,0 +1,148 @@
|
||||
<template> |
||||
<div> |
||||
<v-menu bottom offset-y> |
||||
<template #activator="{on}"> |
||||
<v-btn |
||||
:loading="projectCreation" |
||||
:disabled="projectCreation" |
||||
class="primary" |
||||
x-large |
||||
v-on="on" |
||||
> |
||||
Use template |
||||
<v-icon>mdi-menu-down</v-icon> |
||||
</v-btn> |
||||
</template> |
||||
<v-list> |
||||
<v-list-item dense class="py-2" @click="useTemplate('rest')"> |
||||
<v-list-item-title> |
||||
<v-icon class="mr-1" :color="textColors[7]"> |
||||
mdi-code-json |
||||
</v-icon> |
||||
Create REST Project |
||||
</v-list-item-title> |
||||
</v-list-item> |
||||
<v-list-item dense class="py-2" @click="useTemplate('graphql')"> |
||||
<v-list-item-title> |
||||
<v-icon class="mr-1" :color="textColors[3]"> |
||||
mdi-graphql |
||||
</v-icon> |
||||
Create GQL Project |
||||
</v-list-item-title> |
||||
</v-list-item> |
||||
</v-list> |
||||
</v-menu> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import colors from '~/mixins/colors' |
||||
|
||||
export default { |
||||
name: 'CreateProjectFromTemplateBtn', |
||||
mixins: [colors], |
||||
props: { |
||||
loading: Boolean, |
||||
templateData: Object, |
||||
importData: Object, |
||||
loaderMessage: String |
||||
}, |
||||
data() { |
||||
return { |
||||
projectCreation: false, |
||||
loaderMessagesIndex: 0, |
||||
loaderMessages: [ |
||||
'Setting up new database configs', |
||||
'Inferring database schema', |
||||
'Generating APIs.', |
||||
'Generating APIs..', |
||||
'Generating APIs...', |
||||
'Generating APIs....', |
||||
'Please wait', |
||||
'Please wait.', |
||||
'Please wait..', |
||||
'Please wait...', |
||||
'Please wait..', |
||||
'Please wait.', |
||||
'Please wait', |
||||
'Please wait.', |
||||
'Please wait..', |
||||
'Please wait...', |
||||
'Please wait..', |
||||
'Please wait.', |
||||
'Please wait..', |
||||
'Please wait...' |
||||
] |
||||
} |
||||
}, |
||||
methods: { |
||||
async useTemplate(projectType) { |
||||
// this.$emit('useTemplate', type) |
||||
|
||||
this.projectCreation = true |
||||
try { |
||||
const interv = setInterval(() => { |
||||
debugger |
||||
this.loaderMessagesIndex = this.loaderMessagesIndex < this.loaderMessages.length - 1 ? this.loaderMessagesIndex + 1 : 6 |
||||
this.$emit('update:loaderMessage', this.loaderMessages[this.loaderMessagesIndex]) |
||||
}, 1000) |
||||
|
||||
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', { |
||||
title: this.templateData.title, |
||||
projectType, |
||||
template: this.templateData |
||||
}]) |
||||
|
||||
await this.$store.dispatch('project/ActLoadProjectInfo') |
||||
|
||||
clearInterval(interv) |
||||
if (this.importData) { |
||||
this.$emit('update:loaderMessage', 'Importing excel data to project') |
||||
await this.importDataToProject({ projectId: result.id, projectType, prefix: result.prefix }) |
||||
} |
||||
|
||||
this.$emit('update:loaderMessage', null) |
||||
|
||||
this.projectReloading = false |
||||
|
||||
this.$router.push({ |
||||
path: `/nc/${result.id}`, |
||||
query: { |
||||
new: 1 |
||||
} |
||||
}) |
||||
} catch (e) { |
||||
console.log(e) |
||||
this.$toast.error(e.message).goAway(3000) |
||||
} |
||||
this.projectCreation = false |
||||
}, |
||||
async importDataToProject({ projectId, projectType, prefix = '' }) { |
||||
this.$store.commit('project/MutProjectId', projectId) |
||||
this.$ncApis.setProjectId(projectId) |
||||
|
||||
await Promise.all(Object.entries(this.importData).map(async([table, data]) => { |
||||
await this.$store.dispatch('meta/ActLoadMeta', { |
||||
tn: `${prefix}${table}` |
||||
}) |
||||
|
||||
// todo: get table name properly |
||||
const api = this.$ncApis.get({ |
||||
table: `${prefix}${table}`, |
||||
type: projectType |
||||
}) |
||||
|
||||
for (let i = 0; i < data.length; i += 500) { |
||||
console.log(data[i]) |
||||
await api.insertBulk(data.slice(i, i + 500)) |
||||
} |
||||
})) |
||||
} |
||||
} |
||||
|
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
Loading…
Reference in new issue