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

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

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

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

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

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

@ -1,11 +1,29 @@
<template> <template>
<div class="d-flex align-center"> <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-dialog v-if="templatesModal" v-model="templatesModal">
<v-card height="90vh"> <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-card>
</v-dialog> </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> </div>
</template> </template>
@ -15,29 +33,104 @@ import ProjectTemplates from '~/components/templates/list'
export default { export default {
name: 'TemplatesModal', name: 'TemplatesModal',
components: { ProjectTemplates }, components: { ProjectTemplates },
props: {
hideLabel: Boolean,
value: Boolean,
createProject: Boolean
},
data: () => ({ 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: { methods: {
async importTemplate(template) { async importTemplate(template, projectType) {
try { if (this.createProject) {
const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ this.projectCreation = true
// todo: extract based on active try {
dbAlias: 'db', // this.nodes.dbAlias, const interv = setInterval(() => {
env: '_noco' this.loaderMessagesIndex = this.loaderMessagesIndex < this.loaderMessages.length - 1 ? this.loaderMessagesIndex + 1 : 6
}, 'xcModelsCreateFromTemplate', { }, 1000)
template
}]) const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', {
title: template.title,
if (res && res.tables && res.tables.length) { projectType,
this.$toast.success(`Imported ${res.tables.length} tables successfully`).goAway(3000) template
} else { }])
this.$toast.success('Template imported successfully').goAway(3000)
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": "Create",
"subtext_1_tooltip": "Create a new project", "subtext_1_tooltip": "Create a new project",
"subtext_2": "Create By Connecting <br>To An External Database", "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", "search_project": "Search Project",
"import_metadata": "Import Metadata", "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", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz",
"integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==" "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": { "after": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "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", "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.4.0.tgz",
"integrity": "sha512-S18o4Y9PqI/uabdlT/jI3MY7XBJjNxnfapFIkjkMwpz6qNxLFZOm2b22OMf4ZYDL9lpNWI+Ih4fEMVPwO1KHFQ==" "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": { "chalk": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@ -3876,6 +3917,11 @@
"q": "^1.1.2" "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": { "collection-visit": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "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": { "create-ecdh": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", "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", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" "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": { "expand-brackets": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@ -6086,6 +6146,11 @@
"reusify": "^1.0.4" "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": { "figgy-pudding": {
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", "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", "resolved": "https://registry.npmjs.org/format-thousands/-/format-thousands-1.1.1.tgz",
"integrity": "sha1-eXW+4wM42QBjkNpYMdsLQcMj+/o=" "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": { "fragment-cache": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "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", "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" "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": { "process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -11045,6 +11120,14 @@
"lodash": "^4.17.20" "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": { "ssri": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "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": { "word-wrap": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "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", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==" "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": { "xmlhttprequest-ssl": {
"version": "1.6.3", "version": "1.6.3",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz", "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", "vuelidate": "^0.7.6",
"vuetify-datetime-picker": "^2.1.1", "vuetify-datetime-picker": "^2.1.1",
"vuex-persistedstate": "^3.1.0", "vuex-persistedstate": "^3.1.0",
"xlsx": "^0.17.3",
"xterm": "^4.8.1", "xterm": "^4.8.1",
"xterm-addon-fit": "^0.4.0", "xterm-addon-fit": "^0.4.0",
"xterm-addon-web-links": "^0.4.0" "xterm-addon-web-links": "^0.4.0"

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

@ -190,6 +190,27 @@
}}</span> }}</span>
</v-tooltip> </v-tooltip>
</v-list-item> </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-list>
</v-menu> </v-menu>
</template> </template>
@ -665,6 +686,7 @@
:dialog-show="dialogShow" :dialog-show="dialogShow"
:heading="confirmMessage" :heading="confirmMessage"
/> />
<templates-modal v-model="templatesModal" hide-label create-project />
</v-container> </v-container>
</template> </template>
@ -673,9 +695,11 @@ import dlgLabelSubmitCancel from '../../components/utils/dlgLabelSubmitCancel.vu
import ShareIcons from '../../components/share-icons' import ShareIcons from '../../components/share-icons'
import SponsorMini from '@/components/sponsorMini' import SponsorMini from '@/components/sponsorMini'
import colors from '~/mixins/colors' import colors from '~/mixins/colors'
import TemplatesModal from '~/components/templates/templatesModal'
export default { export default {
components: { components: {
TemplatesModal,
ShareIcons, ShareIcons,
SponsorMini, SponsorMini,
dlgLabelSubmitCancel dlgLabelSubmitCancel
@ -687,6 +711,7 @@ export default {
}, },
data() { data() {
return { return {
templatesModal: false,
overlayVisible: true, overlayVisible: true,
showCommunity: false, showCommunity: false,
project_id: null, project_id: null,
@ -951,6 +976,9 @@ export default {
this.$router.push('/project/0') this.$router.push('/project/0')
} }
}, },
onCreateProjectFromTemplate() {
this.templatesModal = true
},
async importProjectFromJSON() { async importProjectFromJSON() {
}, },
onTourCompletion() { onTourCompletion() {

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

@ -1380,7 +1380,7 @@ export default class NcMetaMgr {
protected async handleRequest(req, res, next) { protected async handleRequest(req, res, next) {
try { try {
const args = req.body; const args = req.body;
let result; let result, postListenerCb;
switch (args.api) { switch (args.api) {
case 'xcPluginDemoDefaults': case 'xcPluginDemoDefaults':
@ -1655,6 +1655,22 @@ export default class NcMetaMgr {
}); });
Tele.emit('evt', { evt_type: 'project:created', xcdb: true }); 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; break;
} }
case 'projectList': case 'projectList':
@ -1914,6 +1930,10 @@ export default class NcMetaMgr {
}); });
} }
if (postListenerCb) {
await postListenerCb();
}
if ( if (
result && result &&
typeof result === 'object' && typeof result === 'object' &&
@ -4330,7 +4350,7 @@ export default class NcMetaMgr {
meta, meta,
tn tn
}, },
api: 'xcM2MRelationCreate' api: 'tableXcModelGet'
}, },
res, res,
user: req.user, user: req.user,

Loading…
Cancel
Save