Browse Source

feat: template creation ui updates

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/760/head
Pranav C 3 years ago
parent
commit
b0c91a2e11
  1. 34
      packages/nc-gui/components/templates/categories.vue
  2. 11
      packages/nc-gui/components/templates/detailed.vue
  3. 111
      packages/nc-gui/components/templates/editor.vue
  4. 83
      packages/nc-gui/components/templates/help.vue
  5. 14
      packages/nc-gui/components/templates/list.vue
  6. 14
      packages/nc-gui/components/templates/templatesModal.vue
  7. 3
      packages/nc-gui/nuxt.config.js
  8. 10
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

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

@ -3,7 +3,7 @@
<v-list dense>
<v-list-item dense>
<v-list-item-subtitle>
<span class="caption" @click="counterLoc++">Categories</span>
<span class="caption" @dblclick="counterLoc++">Categories</span>
</v-list-item-subtitle>
</v-list-item>
<v-list-item-group v-model="category">
@ -11,6 +11,7 @@
<v-list-item-title>
<span
:class="{'font-weight-black' : category === c.category } "
class="body-1"
>
{{
c.category
@ -24,20 +25,43 @@
<v-btn
class="ml-4"
color="grey"
x-small
small
outlined
@click="showTemplateEditor"
>
New template
<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-text-field
v-if="$store.state.templateC >4"
v-if="$store.state.templateE > 3"
v-model="t"
outlined
dense
:full-width="false"
style="width: 135px"
type="password"
class="caption mt-4 ml-1 mr-3"
class="caption mt-4 ml-1 mr-3 ml-4 "
hide-details
/>
</div>

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

@ -1,7 +1,7 @@
<template>
<v-container class="py-0">
<div class="d-flex">
<v-navigation-drawer permanent height="calc(100vh - 40px)">
<div class="d-flex h-100">
<v-navigation-drawer permanent height="100%">
<categories
ref="cat"
:counter.sync="counter"
@ -9,7 +9,7 @@
@input="v => $emit('load-category', v)"
/>
</v-navigation-drawer>
<v-container v-if="templateData" fluid style="height: calc(100vh - 40px ); overflow: auto">
<v-container v-if="templateData" fluid style="height: 100%; overflow: auto">
<v-img
:src="templateData.image_url"
height="200px"
@ -19,7 +19,7 @@
<h2 class="display-2 font-weight-bold my-0 flex-grow-1">
{{ templateData.title }}
</h2>
<v-btn class="primary" x-large @click="useTemplate">
<v-btn :loading="loading" :disabled="loading" class="primary" x-large @click="useTemplate">
Use template
</v-btn>
</div>
@ -29,7 +29,7 @@
<templat-editor
:id="templateId"
:view-mode="$store.state.templateE < 5 && viewMode"
:view-mode="$store.state.templateE < 4 && viewMode"
:template-data.sync="templateData"
@saved="onSaved"
/>
@ -46,6 +46,7 @@ export default {
name: 'ProjectTemplateDetailed',
components: { Categories, TemplatEditor },
props: {
loading: Boolean,
modal: Boolean,
viewMode: Boolean,
id: [String, Number]

111
packages/nc-gui/components/templates/editor.vue

@ -1,5 +1,5 @@
<template>
<div>
<div class="h-100">
<v-toolbar v-if="!viewMode" class="elevation-0">
<!-- <v-text-field
v-model="url"
@ -11,6 +11,24 @@
@keydown.enter="loadUrl"
/>-->
<!-- <v-btn outlined class='ml-1' @click='loadUrl'> Load URL</v-btn>-->
<v-tooltip bottom>
<template #activator="{on}">
<v-btn
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 template from Excel</span>
</v-tooltip>
<v-spacer />
<v-icon class="mr-3" @click="helpModal=true">
@ -36,7 +54,8 @@
<v-btn small outlined class="mr-1" @click="project = {tables : []}">
<v-icon small>
mdi-close
</v-icon> Reset
</v-icon>
Reset
</v-btn>
<!-- <v-icon
:color="$store.getters['github/isAuthorized'] ? '' : 'error'"
@ -45,18 +64,27 @@
>
mdi-github
</v-icon>-->
<v-btn small outlined class="mr-1">
<v-icon small @click="createTablesDialog = true">
<v-btn small outlined class="mr-1" @click="createTablesDialog = true">
<v-icon small>
mdi-plus
</v-icon>
New table
</v-btn>
<!-- <v-btn outlined small class='mr-1' @click='submitTemplate'> Submit Template</v-btn>-->
<v-btn color="primary" outlined small class="mr-1" @click="saveTemplate">
{{ id || localId ? 'Update in' :'Submit to' }} NocoDB
<v-btn
color="primary"
outlined
small
class="mr-1"
:loading="loading"
:disabled="loading"
@click="saveTemplate"
>
{{ id || localId ? 'Update in' : 'Submit to' }} NocoDB
</v-btn>
</v-toolbar>
<v-container class="text-center">
<v-container class="text-center" style="height:calc(100% - 64px);overflow-y: auto">
<v-form ref="form">
<v-row fluid class="justify-center">
<v-col cols="12">
@ -77,13 +105,14 @@
>
<v-text-field
v-if="editableTn[i]"
v-model="table.tn"
:value="table.tn"
class="title"
style="max-width: 300px"
outlinedk
autofocus
dense
hide-details
@input="e => onTableNameUpdate(table, e)"
@click="e => viewMode || e.stopPropagation()"
@blur="$set(editableTn,i, false)"
@keydown.enter=" $set(editableTn,i, false)"
@ -133,14 +162,19 @@
<v-text-field
v-else
:ref="`cn_${table.tn}_${j}`"
v-model="col.cn"
:value="col.cn"
outlined
dense
class="caption"
placeholder="Column name"
hide-details="auto"
:rules="[v => !!v || 'Column name required']"
:rules="[
v => !!v || 'Column name required',
v =>!table.columns.some(c=>c !== col && c.cn === v) || 'Duplicate column not allowed'
]"
@input="e => onColumnNameUpdate(col,e,table.tn)"
/>
</td>
@ -233,7 +267,7 @@
class="caption"
dense
hide-details="auto"
:rules="[v => !!v || 'Related table name required']"
:rules="[v => !!v || 'Related table name required', ...getRules(col, table)]"
:items="isLookupOrRollup(col) ? getRelatedTables(table.tn, isRollup(col)) : project.tables"
:item-text="t => isLookupOrRollup(col) ? `${t.tn} (${t.type})` : t.tn"
:item-value="t => isLookupOrRollup(col) ? t : t.tn"
@ -384,7 +418,7 @@
outlined
dense
label="Project Name"
:rules="[v => !!v || 'Required'] "
:rules="[v => !!v || 'Project name required'] "
/>
</div>
<!--
@ -402,6 +436,7 @@
<v-col>
<v-text-field
v-model="project.category"
:rules="[v => !!v || 'Category name required']"
class="caption"
outlined
dense
@ -424,7 +459,7 @@
class="caption"
outlined
dense
label="Project Tags"
label="Project Description"
@click="counter++"
/>
</div>
@ -496,7 +531,7 @@
fab
large
color="primary"
right="100"
right
style="top:45%"
@click="createTablesDialog = true"
v-on="on"
@ -511,8 +546,10 @@
<script>
import UITypes from '../../../nocodb/build/main/lib/sqlUi/UITypes'
import { uiTypes, getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes'
import GradientGenerator from '~/components/templates/gradientGenerator'
import Help from '~/components/templates/help'
const LinkToAnotherRecord = 'LinkToAnotherRecord'
const Lookup = 'Lookup'
@ -521,13 +558,14 @@ const defaultColProp = {}
export default {
name: 'TemplateEditor',
components: { GradientGenerator },
components: { Help, GradientGenerator },
props: {
id: [Number, String],
viewMode: Boolean,
templateData: Object
},
data: () => ({
loading: false,
localId: null,
valid: false,
url: '',
@ -544,7 +582,7 @@ export default {
createTablesDialog: false,
createTableColumnsDialog: false,
selectedTable: null,
uiTypes,
uiTypes: uiTypes.filter(t => ![UITypes.Formula, UITypes.SpecificDBType].includes(t.name)),
rollupFnList: [
{ text: 'count', value: 'count' },
{ text: 'min', value: 'min' },
@ -819,8 +857,6 @@ export default {
},
async handleKeyDown({ metaKey, key, altKey, shiftKey, ctrlKey }) {
// eslint-disable-next-line no-console
console.log({ metaKey, key, altKey, shiftKey, ctrlKey })
if (!(metaKey && ctrlKey) && !(altKey && shiftKey)) {
return
}
@ -1027,6 +1063,7 @@ export default {
},
async saveTemplate() {
this.loading = true
try {
if (this.id || this.localId) {
await this.$axios.put(`${process.env.NC_API_URL}/api/v1/nc/templates/${this.id || this.localId}`, this.projectTemplate, {
@ -1058,9 +1095,45 @@ export default {
this.$emit('saved')
} catch (e) {
this.$toast.error(e.message).goAway(3000)
} finally {
this.loading = false
}
}
},
getRules(col, table) {
return v => col.uidt !== UITypes.LinkToAnotherRecord || !table.columns.some(c => c !== col && c.uidt === UITypes.LinkToAnotherRecord && c.type === col.type && c.rtn === col.rtn) || 'Duplicate relation is not allowed'
},
onTableNameUpdate(oldTable, newVal) {
const oldVal = oldTable.tn
this.$set(oldTable, 'tn', newVal)
for (const table of this.project.tables) {
for (const col of table.columns) {
if (col.uidt === UITypes.LinkToAnotherRecord) {
if (col.rtn === oldVal) {
this.$set(col, 'rtn', newVal)
}
} else if (col.uidt === UITypes.Rollup || col.uidt === UITypes.Lookup) {
if (col.rtn && col.rtn.tn === oldVal) {
this.$set(col.rtn, 'tn', newVal)
}
}
}
}
},
onColumnNameUpdate(oldCol, newVal, tn) {
const oldVal = oldCol.cn
this.$set(oldCol, 'cn', newVal)
for (const table of this.project.tables) {
for (const col of table.columns) {
if (col.uidt === UITypes.Rollup || col.uidt === UITypes.Lookup) {
if (col.rtn && col.rcn === oldVal && col.rtn.tn === tn) {
this.$set(col, 'rcn', newVal)
}
}
}
}
}
}
}
</script>

83
packages/nc-gui/components/templates/help.vue

@ -0,0 +1,83 @@
<template>
<v-dialog v-model='localState' max-width='900'>
<v-card>
<v-card-title>
Shortcuts
</v-card-title>
<v-card-text>
<v-simple-table v-slot dense>
<thead>
<tr>
<th></th>
<th class=' pa-2'>Mac OS</th>
<th class=' pa-2'>Windows / Linux</th>
</tr>
</thead>
<tbody>
<tr v-for='(short,i) in shortcuts' :key='i'>
<td class='caption'>{{ short.description }}</td>
<td class=' caption pa-2' v-html='short.mac'></td>
<td class=' caption pa-2' v-html='short.windows'></td>
</tr>
</tbody>
</v-simple-table>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'Help',
props: {
value: Boolean
},
data() {
return {
shortcuts: [{
description: 'Add tables',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>T</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>T</kbd>'
},{
description: 'Add columns',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>C</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd>'
},{
description: 'Add new column row',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>A</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd>'
},{
description: 'Table navigation',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>Arrow Down</kbd> <br>AND<br> <kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>Arrow Up</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Arrow Down</kbd> <br>AND<br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Arrow Up</kbd>'
},{
description: 'Column navigation',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>Arrow Right</kbd> <br>AND<br> <kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>Arrow Left</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Arrow Right</kbd> <br>AND<br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Arrow Right</kbd>'
},{
description: 'Copy json to clipboard',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>J</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd>'
},{
description: 'Submit template',
mac: '<kbd>Command</kbd> + <kbd>Control</kbd> + <kbd>S</kbd>',
windows: '<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd>'
},]
}
},
computed: {
localState: {
get() {
return this.value
}, set(val) {
this.$emit('input', val)
}
}
}
}
</script>
<style scoped>
</style>

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

@ -1,7 +1,7 @@
<template>
<v-container v-if="newEditor || !modal || selectedId === null " class="py-0">
<div class="d-flex">
<v-navigation-drawer permanent height="calc(100vh - 40px)">
<div class="d-flex h-100">
<v-navigation-drawer permanent height="100% ">
<categories
ref="cat"
v-model="category"
@ -10,8 +10,8 @@
@showTemplateEditor="newEditor = true"
/>
</v-navigation-drawer>
<template-editor v-if="newEditor" style="width:100%" @saved="onSaved" />
<v-container v-else fluid style="height: calc(100vh - 40px); overflow: auto">
<template-editor v-if="newEditor" style="width:100%; height: 100%; " @saved="onSaved" />
<v-container v-else fluid style="height: 100%; overflow: auto">
<v-row
v-if="templateList && templateList.length"
class="align-stretch"
@ -33,7 +33,7 @@
>
<v-img
:src="template.image_url"
height="200px"
height="50px"
:style="{ background: template.image_url }"
/>
@ -60,6 +60,7 @@
<project-template-detailed
v-else
:id="selectedId"
:loading="loading"
:counter="counter"
:modal="modal"
:view-mode="counter < 5"
@ -81,7 +82,8 @@ export default {
name: 'ProjectTemplates',
components: { TemplateEditor, Categories, ProjectTemplateDetailed },
props: {
modal: Boolean
modal: Boolean,
loading: Boolean
},
data: () => ({
category: null,

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

@ -2,8 +2,8 @@
<div>
<span v-ripple class="caption font-weight-bold pointer" @click="templatesModal = true">Templates</span>
<v-dialog v-if="templatesModal" v-model="templatesModal">
<v-card>
<project-templates modal @import="importTemplate" />
<v-card height="90vh">
<project-templates style="height:90vh" modal :loading="loading" @import="importTemplate" />
</v-card>
</v-dialog>
</div>
@ -16,7 +16,8 @@ export default {
name: 'TemplatesModal',
components: { ProjectTemplates },
data: () => ({
templatesModal: false
templatesModal: false,
loading: false
}),
methods: {
async importTemplate(template) {
@ -31,13 +32,6 @@ export default {
if (res && res.tables && res.tables.length) {
this.$toast.success(`Imported ${res.tables.length} tables successfully`).goAway(3000)
// await this.$router.push({
// query: {
// ...(this.$route.query || {}),
// type: 'table',
// name: res.tables[0]._tn
// }
// })
} else {
this.$toast.success('Template imported successfully').goAway(3000)
}

3
packages/nc-gui/nuxt.config.js

@ -210,8 +210,7 @@ export default {
],
env: {
EE: !!process.env.EE,
NC_API_URL: 'http://localhost:3001'
// NC_API_URL: 'https://nocodb.com'
NC_API_URL: 'https://nocodb.com'
},
pwa: {
workbox: {

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

@ -4089,6 +4089,16 @@ export default class NcMetaMgr {
});
parser.parse();
const existingTables = parser.tables.filter(t => apiBuilder.getMeta(t.tn));
if (existingTables?.length) {
throw new Error(
`Import unsuccessful : following tables '${existingTables
.map(t => t._tn)
.join(', ')}' already exists`
);
}
for (const table of parser.tables) {
console.log(table);
// create table and trigger listener

Loading…
Cancel
Save