Browse Source

feat: create project from template

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/763/head
Pranav C 3 years ago
parent
commit
3fb9df3fdf
  1. 55
      packages/nc-gui/components/import/excelImport.vue
  2. 143
      packages/nc-gui/components/templates/categories.vue
  3. 110
      packages/nc-gui/components/templates/detailed.vue
  4. 25
      packages/nc-gui/components/templates/list.vue
  5. 135
      packages/nc-gui/components/templates/templatesModal.vue
  6. 3
      packages/nc-gui/lang/en.json
  7. 117
      packages/nc-gui/package-lock.json
  8. 1
      packages/nc-gui/package.json
  9. 28
      packages/nc-gui/pages/projects/index.vue
  10. 24
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

55
packages/nc-gui/components/import/excelImport.vue

@ -0,0 +1,55 @@
<template>
<v-tooltip bottom>
<template #activator="{on}">
<input ref="file" type="file" style="display: none" accept=".xlsx, .xls" @change="_change($event)">
<v-btn
small
outlined
v-on="on"
@click="$refs.file.click()"
>
<v-icon small class="mr-1">
mdi-file-excel-outline
</v-icon>
Import
</v-btn>
</template>
<span class="caption">Create template from Excel</span>
</v-tooltip>
</template>
<script>
import XLSX from 'xlsx'
export default {
name: 'ExcelImport',
methods: {
_change(file) {
const files = file.target.files
if (files && files[0]) { this._file(files[0]) }
},
_file(file) {
const reader = new FileReader()
reader.onload = (e) => {
const ab = e.target.result
const wb = XLSX.read(new Uint8Array(ab), { type: 'array' })
// const res = {}
// iterate each sheet
// each sheet repensents each table
for (let i = 0; i < wb.SheetNames.length; i++) {
const sheet = wb.SheetNames[i]
const ws = wb.Sheets[sheet]
const rows = XLSX.utils.sheet_to_json(ws, { header: 1 })
console.log(rows)
}
}
reader.readAsArrayBuffer(file)
}
}
}
</script>
<style scoped>
</style>

143
packages/nc-gui/components/templates/categories.vue

@ -1,71 +1,81 @@
<template>
<div class="h-100">
<v-list dense>
<v-list-item dense>
<v-list-item-subtitle>
<span class="caption" @dblclick="counterLoc++">Categories</span>
</v-list-item-subtitle>
</v-list-item>
<v-list-item-group v-model="category">
<v-list-item v-for="c in categories" :key="c.category" :value="c.category" dense>
<v-list-item-title>
<span
:class="{'font-weight-black' : category === c.category } "
class="body-1"
>
{{
c.category
}}
</span> <span class="grey--text ">({{ c.count }})</span>
</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-list>
<!-- v-if="counter > 4"-->
<v-btn
class="ml-4"
color="grey"
small
outlined
@click="showTemplateEditor"
<div class="">
<div
v-if="loading"
class="pr-4"
>
<v-icon class="mr-1" small>
mdi-plus
</v-icon> New template
</v-btn>
<v-skeleton-loader type="text" class="mx-2 mt-4 mr-14" />
<v-skeleton-loader type="text@10" class="mx-2 mt-4" />
<v-skeleton-loader type="text@2" class="mx-2 mt-4 mr-14" />
</div>
<template v-else>
<v-list dense>
<v-list-item dense>
<v-list-item-subtitle>
<span class="caption" @dblclick="counterLoc++">Categories</span>
</v-list-item-subtitle>
</v-list-item>
<v-list-item-group v-model="category">
<v-list-item v-for="c in categories" :key="c.category" :value="c.category" dense>
<v-list-item-title>
<span
:class="{'font-weight-black' : category === c.category } "
class="body-1"
>
{{
c.category
}}
</span> <span class="grey--text ">({{ c.count }})</span>
</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-list>
<!-- v-if="counter > 4"-->
<v-btn
class="ml-4"
color="grey"
small
outlined
@click="showTemplateEditor"
>
<v-icon class="mr-1" small>
mdi-plus
</v-icon> New template
</v-btn>
<v-tooltip bottom>
<template #activator="{on}">
<v-btn
class="ml-4 mt-4"
color="grey"
small
outlined
v-on="on"
@click="$toast.info('Happy hacking!').goAway(3000)"
>
<v-icon small class="mr-1">
mdi-file-excel-outline
</v-icon>
Import
</v-btn>
</template>
<span class="caption">Create templates from multiple Excel files</span>
</v-tooltip>
<v-tooltip bottom>
<template #activator="{on}">
<v-btn
class="ml-4 mt-4"
color="grey"
small
outlined
v-on="on"
@click="$toast.info('Happy hacking!').goAway(3000)"
>
<v-icon small class="mr-1">
mdi-file-excel-outline
</v-icon>
Import
</v-btn>
</template>
<span class="caption">Create templates from multiple Excel files</span>
</v-tooltip>
<v-text-field
v-if="$store.state.templateE > 3"
v-model="t"
autocomplete="new-password"
name="nc"
outlined
dense
:full-width="false"
style="width: 135px"
type="password"
class="caption mt-4 ml-1 mr-3 ml-4 "
hide-details
/>
<v-text-field
v-if="$store.state.templateE > 3"
v-model="t"
autocomplete="new-password"
name="nc"
outlined
dense
:full-width="false"
style="width: 135px"
type="password"
class="caption mt-4 ml-1 mr-3 ml-4 "
hide-details
/>
</template>
</div>
</template>
@ -75,7 +85,8 @@ export default {
name: 'Categories',
props: { value: String, counter: Number },
data: () => ({
categories: []
categories: [],
loading: false
}),
computed: {
counterLoc: {
@ -108,12 +119,14 @@ export default {
},
methods: {
async loadCategories() {
this.loading = true
try {
const res = await this.$axios.get(`${process.env.NC_API_URL}/api/v1/nc/templates/categories`)
this.categories = res.data
} catch (e) {
console.log(e)
}
this.loading = false
},
showTemplateEditor() {
this.$emit('showTemplateEditor')

110
packages/nc-gui/components/templates/detailed.vue

@ -9,30 +9,80 @@
@input="v => $emit('load-category', v)"
/>
</v-navigation-drawer>
<v-container v-if="templateData" fluid style="height: 100%; overflow: auto">
<v-img
:src="templateData.image_url"
height="200px"
:style="{ background: templateData.image_url }"
/>
<div class="d-flex align-center mt-10">
<h2 class="display-2 font-weight-bold my-0 flex-grow-1">
{{ templateData.title }}
</h2>
<v-btn :loading="loading" :disabled="loading" class="primary" x-large @click="useTemplate">
Use template
</v-btn>
</div>
<p class="caption mt-10">
{{ templateData.description }}
</p>
<v-container fluid style="height: 100%; overflow: auto">
<template v-if="loadingTemplate">
<v-skeleton-loader type="image" />
<div class="d-flex mt-2 align-center">
<v-skeleton-loader style="width:200px; " type="card-heading" />
<v-spacer />
<v-skeleton-loader type="button" />
</div>
<v-skeleton-loader type="paragraph" class="mt-2" />
<v-skeleton-loader type="table" class="mt-2" />
</template>
<template v-else-if="templateData">
<v-img
:src="templateData.image_url"
height="200px"
:style="{ background: templateData.image_url }"
/>
<div class="d-flex align-center mt-10">
<h2 class="display-2 font-weight-bold my-0 flex-grow-1">
{{ templateData.title }}
</h2>
<v-menu v-if="createProject" bottom offset-y>
<template #activator="{on}">
<v-btn
:loading="loading"
:disabled="loading"
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>
<v-btn
v-else
:loading="loading"
:disabled="loading"
class="primary"
x-large
@click="useTemplate"
>
Use template
</v-btn>
</div>
<p class="caption mt-10">
{{ templateData.description }}
</p>
<templat-editor
:id="templateId"
:view-mode="$store.state.templateE < 4 && viewMode"
:template-data.sync="templateData"
@saved="onSaved"
/>
<templat-editor
:id="templateId"
:view-mode="$store.state.templateE < 4 && viewMode"
:template-data.sync="templateData"
@saved="onSaved"
/>
</template>
</v-container>
</div>
</v-container>
@ -41,18 +91,20 @@
<script>
import TemplatEditor from '~/components/templates/editor'
import Categories from '~/components/templates/categories'
import colors from '~/mixins/colors'
export default {
name: 'ProjectTemplateDetailed',
components: { Categories, TemplatEditor },
mixins: [colors],
props: {
loading: Boolean,
modal: Boolean,
viewMode: Boolean,
id: [String, Number]
id: [String, Number],
createProject: Boolean
},
data: () => ({ templateData: null, counter: 0 }),
data: () => ({ templateData: null, counter: 0, loadingTemplate: false }),
computed: {
templateId() {
return this.modal ? this.id : this.$route.params.id
@ -63,6 +115,7 @@ export default {
},
methods: {
async loadTemplateData() {
this.loadingTemplate = true
try {
const res = await this.$axios.get(`${process.env.NC_API_URL}/api/v1/nc/templates/${this.templateId}`)
const data = res.data
@ -70,10 +123,11 @@ export default {
} catch (e) {
console.log(e)
}
this.loadingTemplate = false
},
useTemplate() {
useTemplate(apiType) {
if (this.modal) {
this.$emit('import', this.templateData)
this.$emit('import', this.templateData, apiType)
}
},
async onSaved() {

25
packages/nc-gui/components/templates/list.vue

@ -12,9 +12,23 @@
</v-navigation-drawer>
<template-editor v-if="newEditor" style="width:100%; height: 100%; " @saved="onSaved" />
<v-container v-else fluid style="height: 100%; overflow: auto">
{{ category }}
<v-row v-if="templatesLoading">
<v-col
v-for="i in 10"
:key="i"
sm="8"
offset-sm="2"
offset-md="0"
md="6"
lg="4"
xl="3"
>
<v-skeleton-loader type="card" />
</v-col>
</v-row>
<v-row
v-if="templateList && templateList.length"
v-else-if="templateList && templateList.length"
class="align-stretch"
>
<v-col
@ -61,6 +75,7 @@
<project-template-detailed
v-else
:id="selectedId"
:create-project="createProject"
:loading="loading"
:counter="counter"
:modal="modal"
@ -84,9 +99,11 @@ export default {
components: { TemplateEditor, Categories, ProjectTemplateDetailed },
props: {
modal: Boolean,
loading: Boolean
loading: Boolean,
createProject: Boolean
},
data: () => ({
templatesLoading: false,
category: null,
selectedId: null,
templateListLoc: [],
@ -103,12 +120,14 @@ export default {
},
methods: {
async loadTemplates() {
this.templatesLoading = true
try {
const res = await this.$axios.get(`${process.env.NC_API_URL}/api/v1/nc/templates`)
this.templateListLoc = res.data.data
} catch (e) {
console.log(e)
}
this.templatesLoading = false
},
getShortDescription(str) {
if (!str || str.length < 200) {

135
packages/nc-gui/components/templates/templatesModal.vue

@ -1,11 +1,29 @@
<template>
<div class="d-flex align-center">
<span v-ripple class="caption font-weight-bold pointer" @click="templatesModal = true">Templates</span>
<span
v-if="!hideLabel"
v-ripple
class="caption font-weight-bold pointer"
@click="templatesModal = true"
>Templates</span>
<v-dialog v-if="templatesModal" v-model="templatesModal">
<v-card height="90vh">
<project-templates style="height:90vh" modal :loading="loading" @import="importTemplate" />
<project-templates
:create-project="createProject"
style="height:90vh"
modal
:loading="loading"
@import="importTemplate"
/>
</v-card>
</v-dialog>
<v-overlay :value="projectCreation" z-index="99999" opacity=".9">
<div class="d-flex flex-column align-center">
<v-progress-circular indeterminate size="100" width="15" class="mb-10" />
<span class="title">{{ loaderMessages[loaderMessagesIndex] }}</span>
</div>
</v-overlay>
</div>
</template>
@ -15,29 +33,104 @@ import ProjectTemplates from '~/components/templates/list'
export default {
name: 'TemplatesModal',
components: { ProjectTemplates },
props: {
hideLabel: Boolean,
value: Boolean,
createProject: Boolean
},
data: () => ({
templatesModal: false,
loading: false
loading: false,
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...'
]
}),
computed: {
templatesModal: {
get() {
return this.value
},
set(v) {
this.$emit('input', v)
}
}
},
methods: {
async importTemplate(template) {
try {
const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// todo: extract based on active
dbAlias: 'db', // this.nodes.dbAlias,
env: '_noco'
}, 'xcModelsCreateFromTemplate', {
template
}])
if (res && res.tables && res.tables.length) {
this.$toast.success(`Imported ${res.tables.length} tables successfully`).goAway(3000)
} else {
this.$toast.success('Template imported successfully').goAway(3000)
async importTemplate(template, projectType) {
if (this.createProject) {
this.projectCreation = true
try {
const interv = setInterval(() => {
this.loaderMessagesIndex = this.loaderMessagesIndex < this.loaderMessages.length - 1 ? this.loaderMessagesIndex + 1 : 6
}, 1000)
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', {
title: template.title,
projectType,
template
}])
await this.$store.dispatch('project/ActLoadProjectInfo')
clearInterval(interv)
this.projectReloading = false
if (this.$store.state.project.projectInfo.firstUser || this.$store.state.project.projectInfo.authType === 'masterKey') {
return this.$router.push({
path: '/user/authentication/signup'
})
}
this.$router.push({
path: `/nc/${result.id}`,
query: {
new: 1
}
})
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
this.projectCreation = false
} else {
try {
const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// todo: extract based on active
dbAlias: 'db', // this.nodes.dbAlias,
env: '_noco'
}, 'xcModelsCreateFromTemplate', {
template
}])
if (res && res.tables && res.tables.length) {
this.$toast.success(`Imported ${res.tables.length} tables successfully`).goAway(3000)
} else {
this.$toast.success('Template imported successfully').goAway(3000)
}
this.templatesModal = false
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
this.templatesModal = false
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
}
}

3
packages/nc-gui/lang/en.json

@ -7,7 +7,8 @@
"subtext_1": "Create",
"subtext_1_tooltip": "Create a new project",
"subtext_2": "Create By Connecting <br>To An External Database",
"subtext_2_tooltip": "Supports MySQL, PostgreSQL, SQL Server & SQLite"
"subtext_2_tooltip": "Supports MySQL, PostgreSQL, SQL Server & SQLite",
"from_template": "Create Project from template"
},
"search_project": "Search Project",
"import_metadata": "Import Metadata",

117
packages/nc-gui/package-lock.json generated

@ -2848,6 +2848,15 @@
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz",
"integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w=="
},
"adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
@ -3744,6 +3753,38 @@
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.4.0.tgz",
"integrity": "sha512-S18o4Y9PqI/uabdlT/jI3MY7XBJjNxnfapFIkjkMwpz6qNxLFZOm2b22OMf4ZYDL9lpNWI+Ih4fEMVPwO1KHFQ=="
},
"cfb": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.1.tgz",
"integrity": "sha512-wT2ScPAFGSVy7CY+aauMezZBnNrfnaLSrxHUHdea+Td/86vrk6ZquggV+ssBR88zNs0OnBkL2+lf9q0K+zVGzQ==",
"requires": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0",
"printj": "~1.3.0"
},
"dependencies": {
"adler-32": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.0.tgz",
"integrity": "sha512-f5nltvjl+PRUh6YNfUstRaXwJxtfnKEWhAWWlmKvh+Y3J2+98a0KKVYDEhz6NdKGqswLhjNGznxfSsZGOvOd9g==",
"requires": {
"printj": "~1.2.2"
},
"dependencies": {
"printj": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.2.3.tgz",
"integrity": "sha512-sanczS6xOJOg7IKDvi4sGOUOe7c1tsEzjwlLFH/zgwx/uyImVM9/rgBkc8AfiQa/Vg54nRd8mkm9yI7WV/O+WA=="
}
}
},
"printj": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.3.0.tgz",
"integrity": "sha512-017o8YIaz8gLhaNxRB9eBv2mWXI2CtzhPJALnQTP+OPpuUfP0RMWqr/mHCzqVeu1AQxfzSfAtAq66vKB8y7Lzg=="
}
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@ -3876,6 +3917,11 @@
"q": "^1.1.2"
}
},
"codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -4134,6 +4180,15 @@
}
}
},
"crc-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz",
"integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==",
"requires": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
}
},
"create-ecdh": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
@ -5873,6 +5928,11 @@
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw="
},
"exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
"integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw=="
},
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@ -6086,6 +6146,11 @@
"reusify": "^1.0.4"
}
},
"fflate": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.1.tgz",
"integrity": "sha512-VYM2Xy1gSA5MerKzCnmmuV2XljkpKwgJBKezW+495TTnTCh1x5HcYa1aH8wRU/MfTGhW4ziXqgwprgQUVl3Ohw=="
},
"figgy-pudding": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@ -6275,6 +6340,11 @@
"resolved": "https://registry.npmjs.org/format-thousands/-/format-thousands-1.1.1.tgz",
"integrity": "sha1-eXW+4wM42QBjkNpYMdsLQcMj+/o="
},
"frac": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="
},
"fragment-cache": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@ -9819,6 +9889,11 @@
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA=="
},
"printj": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ=="
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -11045,6 +11120,14 @@
"lodash": "^4.17.20"
}
},
"ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"requires": {
"frac": "~1.1.2"
}
},
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@ -13109,6 +13192,16 @@
}
}
},
"wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="
},
"word": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@ -13245,6 +13338,30 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg=="
},
"xlsx": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.17.3.tgz",
"integrity": "sha512-dGZKfyPSXfnoITruwisuDVZkvnxhjgqzWJXBJm2Khmh01wcw8//baRUvhroVRhW2SLbnlpGcCZZbeZO1qJgMIw==",
"requires": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.15.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.7.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"dependencies": {
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
}
}
},
"xmlhttprequest-ssl": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz",

1
packages/nc-gui/package.json

@ -55,6 +55,7 @@
"vuelidate": "^0.7.6",
"vuetify-datetime-picker": "^2.1.1",
"vuex-persistedstate": "^3.1.0",
"xlsx": "^0.17.3",
"xterm": "^4.8.1",
"xterm-addon-fit": "^0.4.0",
"xterm-addon-web-links": "^0.4.0"

28
packages/nc-gui/pages/projects/index.vue

@ -190,6 +190,27 @@
}}</span>
</v-tooltip>
</v-list-item>
<v-divider />
<v-list-item
title
class="pt-2 nc-create-project-from-template"
@click="onCreateProjectFromTemplate()"
>
<v-list-item-icon class="mr-2">
<v-icon small class="">
mdi-checkbox-multiple-blank
</v-icon>
</v-list-item-icon>
<v-list-item-title>
<!-- Create By Connecting <br>To An External Database -->
<span
class="caption font-weight-regular"
v-html="
$t('projects.create_new_project_button.from_template')
"
/>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
@ -665,6 +686,7 @@
:dialog-show="dialogShow"
:heading="confirmMessage"
/>
<templates-modal v-model="templatesModal" hide-label create-project />
</v-container>
</template>
@ -673,9 +695,11 @@ import dlgLabelSubmitCancel from '../../components/utils/dlgLabelSubmitCancel.vu
import ShareIcons from '../../components/share-icons'
import SponsorMini from '@/components/sponsorMini'
import colors from '~/mixins/colors'
import TemplatesModal from '~/components/templates/templatesModal'
export default {
components: {
TemplatesModal,
ShareIcons,
SponsorMini,
dlgLabelSubmitCancel
@ -687,6 +711,7 @@ export default {
},
data() {
return {
templatesModal: false,
overlayVisible: true,
showCommunity: false,
project_id: null,
@ -951,6 +976,9 @@ export default {
this.$router.push('/project/0')
}
},
onCreateProjectFromTemplate() {
this.templatesModal = true
},
async importProjectFromJSON() {
},
onTourCompletion() {

24
packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

@ -1380,7 +1380,7 @@ export default class NcMetaMgr {
protected async handleRequest(req, res, next) {
try {
const args = req.body;
let result;
let result, postListenerCb;
switch (args.api) {
case 'xcPluginDemoDefaults':
@ -1655,6 +1655,22 @@ export default class NcMetaMgr {
});
Tele.emit('evt', { evt_type: 'project:created', xcdb: true });
postListenerCb = async () => {
if (args?.args?.template) {
await this.xcModelsCreateFromTemplate(
{
dbAlias: 'db', // this.nodes.dbAlias,
env: '_noco',
project_id: result?.id,
args: {
template: args?.args?.template
}
},
req
);
}
};
break;
}
case 'projectList':
@ -1914,6 +1930,10 @@ export default class NcMetaMgr {
});
}
if (postListenerCb) {
await postListenerCb();
}
if (
result &&
typeof result === 'object' &&
@ -4330,7 +4350,7 @@ export default class NcMetaMgr {
meta,
tn
},
api: 'xcM2MRelationCreate'
api: 'tableXcModelGet'
},
res,
user: req.user,

Loading…
Cancel
Save