< template >
< div class = "h-100" >
< v -toolbar v-if ="!viewMode" class="elevation-0" >
< slot name = "toolbar" >
<!-- < v -text -field
v - model = "url"
clearable
placeholder = "Enter template url"
outlined
hide - details
dense
@ keydown . enter = "loadUrl"
/ > - - >
<!-- < v -btn outlined class = 'ml-1' @ click = 'loadUrl' > Load URL < / v - b t n > - - >
< 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 - i c o n >
Import
< / v - b t n >
< / template >
< span class = "caption" > Create template from Excel < / span >
< / v - t o o l t i p >
< v -spacer / >
< v -icon class = "mr-3" @click ="helpModal=true" >
mdi - information - outline
< / v - i c o n >
<!-- < v -icon class = "mr-3" @click ="openUrl" >
mdi - web
< / v - i c o n > - - >
<!-- < v -tooltip bottom >
< template # activator = "{on}" >
< v -icon
class = "mr-3"
v - on = "on"
@ click = "url = '',project.tables= []"
>
mdi - close - circle - outline
< / v - i c o n >
< / template >
< span class = "caption" > Reset template < / span >
< / v - t o o l t i p > - - >
< v -btn small outlined class = "mr-1" @ click = "project = {tables : []}" >
< v -icon small >
mdi - close
< / v - i c o n >
Reset
< / v - b t n >
<!-- < v -icon
: color = "$store.getters['github/isAuthorized'] ? '' : 'error'"
class = "mr-3"
@ click = "githubConfigForm = !githubConfigForm"
>
mdi - github
< / v - i c o n > - - >
< v -btn small outlined class = "mr-1" @ click = "createTablesDialog = true" >
< v -icon small >
mdi - plus
< / v - i c o n >
New table
< / v - b t n >
<!-- < v -btn outlined small class = 'mr-1' @ click = 'submitTemplate' > Submit Template < / v - b t n > - - >
< v -btn
color = "primary"
outlined
small
class = "mr-1"
: loading = "loading"
: disabled = "loading"
@ click = "saveTemplate"
>
{ { id || localId ? 'Update in' : 'Submit to' } } NocoDB
< / v - b t n >
< / slot >
< / v - t o o l b a r >
< 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" >
< v -card >
< v -card -text >
< v -expansion -panels
v - if = "project.tables && project.tables.length"
v - model = "expansionPanel"
: multiple = "viewMode"
accordion
>
< v -expansion -panel
v - for = "(table, i) in project.tables"
: key = "i"
>
< v -expansion -panel -header
: id = "`tn_${table.tn}`"
>
< v -text -field
v - if = "editableTn[i]"
: 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)"
/ >
< span
v - else
class = "title"
@ click = "e => viewMode || (e.stopPropagation() , $set(editableTn,i, true))"
>
< v -icon color = "primary lighten-1" > mdi - table < / v - i c o n >
{ { table . tn } }
< / span >
< v -spacer / >
< v -icon v-if ="!viewMode" class="flex-grow-0 mr-2" small color="grey" @click.stop="deleteTable(i)" >
mdi - delete
< / v - i c o n >
< / v - e x p a n s i o n - p a n e l - h e a d e r >
< v -expansion -panel -content >
<!-- < v -toolbar >
< v -spacer > < / v - s p a c e r >
< v -btn outlined small @ click = 'showColCreateDialog(table,i)' > New Column
< / v - b t n >
< / v - t o o l b a r > - - >
< template >
< v -simple -table v-if ="table.columns.length" dense class="my-4" >
< thead >
< tr >
< th class = "caption text-left pa-3" >
Column Name
< / th >
< th class = "caption text-left pa-3" colspan = "4" >
Column Type
< / th >
< th / >
<!-- < th class = 'text-center' > Related Table < / th > -- >
<!-- < th class = 'text-center' > Related Column < / th > -- >
< / tr >
< / thead >
< tbody >
< tr v-for ="(col,j) in table.columns" :key="j" :data-exp="i" >
< td class = "pa-3 text-left" : style = "{width:viewMode ? '33%' : ''}" >
< span v-if ="viewMode" class="body-1 " >
{ { col . cn } }
< / span >
< v -text -field
v - else
: ref = "`cn_${table.tn}_${j}`"
: value = "col.cn"
outlined
dense
class = "caption"
placeholder = "Column name"
hide - details = "auto"
: 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 >
< template v-if ="viewMode" >
< td
: style = "{width:viewMode && isRelation(col) || isLookupOrRollup(col) ? '33%' : ''}"
: colspan = "isRelation(col) || isLookupOrRollup(col) ? 3 : 5"
class = "text-left"
>
< v -icon small >
{ { getIcon ( col . uidt ) } }
< / v - i c o n >
< span class = "caption" > { { col . uidt } } < / span >
< / td >
< td v-if ="isRelation(col) || isLookupOrRollup(col)" class="text-left" >
< span
v - if = "isRelation(col)"
class = "caption pointer primary--text"
@ click = "navigateToTable(col.rtn)"
>
{ { col . rtn } }
< / span >
< template
v - else - if = "isLookup(col)"
>
< span
class = "caption pointer primary--text"
@ click = "navigateToTable(col.rtn && col.rtn.tn)"
>
{ { col . rtn && col . rtn . tn } }
< / span > < span class = "caption" > ( { { col . rcn } } ) < / span >
< / template >
< template
v - else - if = "isRollup(col)"
>
< span
class = "caption pointer primary--text"
@ click = "navigateToTable(col.rtn && col.rtn.tn)"
>
{ { col . rtn && col . rtn . tn } }
< / span > < span class = "caption" > ( { { col . fn } } ) < / span >
< / template >
< / td >
< / template >
< template v-else >
< td
class = "pa-3 text-left"
: colspan = " isLookupOrRollup ( col ) || isRelation ( col ) || isSelect ( col ) ? ( isRollup ( col ) ?
1 : 2 ) : 4 "
>
< v -autocomplete
: ref = "`uidt_${table.tn}_${j}`"
: value = "col.uidt"
placeholder = "Column Datatype"
outlined
dense
class = "caption"
hide - details = "auto"
: rules = "[v => !!v || 'Column data type required']"
: items = " col . uidt === 'ForeignKey' ? [ ... uiTypes , {
name : 'ForeignKey' ,
icon : 'mdi-link-variant' ,
virtual : 1
} ] : uiTypes "
item - text = "name"
item - value = "name"
@ input = "v => onUidtChange(col.uidt,v,col,table)"
>
< template # item = "{item:{name}}" >
< v -chip v -if = " colors [ name ] " :color ="colors[name]" small >
{ { name } }
< / v - c h i p >
< span v -else class = "caption" > { { name } } < / span >
< / template >
< template # selection = "{item:{name}}" >
< v -chip v -if = " colors [ name ] " :color ="colors[name]" small >
{ { name } }
< / v - c h i p >
< span v -else class = "caption" > { { name } } < / span >
< / template >
< / v - a u t o c o m p l e t e >
< / td >
< template
v - if = "isRelation(col) || isLookupOrRollup(col)"
>
< td class = "pa-3 text-left" >
< v -autocomplete
: value = "col.rtn"
placeholder = "Related table"
outlined
class = "caption"
dense
hide - details = "auto"
: 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"
: value - comparator = "compareRel"
@ input = "v => onRtnChange(col.rtn,v, col, table)"
/ >
< / td >
< td v-if ="isRelation(col)" class="pa-3" >
< template v-if ="col.uidt !== 'ForeignKey'" >
< span
v - if = "viewMode"
class = "caption"
>
<!-- { { col . type } } -- >
< / span >
< v -autocomplete
v - else
: value = "col.type"
placeholder = "Relation Type"
outlined
class = "caption"
dense
hide - details = "auto"
: rules = "[v => !!v || 'Relation type required']"
: items = "[{text:'Many To Many', value:'mm'},{text:'Has Many', value:'hm'}]"
@ input = "v => onRTypeChange(col.type, v, col,table)"
/ >
< / template >
< / td >
< td v-if ="isLookupOrRollup(col)" class="pa-3" >
< span
v - if = "viewMode"
class = "caption"
>
{ { col . rcn } }
< / span >
< v -autocomplete
v - else
v - model = "col.rcn"
placeholder = "Related table column"
outlined
dense
class = "caption"
hide - details = "auto"
: rules = "[v => !!v || 'Related column name required']"
: items = "(project.tables.find(t => t.tn === (col.rtn && col.rtn.tn || col.rtn)) || {columns:[]}).columns.filter(v=> !isVirtual(v))"
item - text = "cn"
item - value = "cn"
/ >
< / td >
< td v-if ="isRollup(col)" class="pa-3" >
< span
v - if = "viewMode"
class = "caption"
>
{ { col . fn } }
< / span >
< v -autocomplete
v - else
v - model = "col.fn"
placeholder = "Rollup function"
outlined
dense
class = "caption"
hide - details = "auto"
: rules = "[v => !!v || 'Rollup aggregate function name required']"
: items = "rollupFnList"
/ >
< / td >
< / template >
< template
v - if = "isSelect(col)"
>
< td class = "pa-3 text-left" colspan = "2" >
< span
v - if = "viewMode"
class = "caption"
>
{ { col . dtxp } }
< / span >
< v -text -field
v - model = "col.dtxp"
placeholder = "Select options"
outlined
class = "caption"
dense
hide - details
/ >
< / td >
< / template >
< td >
< v -icon
v - if = "!viewMode"
class = "flex-grow-0"
small
color = "grey"
@ click . stop = "deleteTableColumn(i,j, col, table)"
>
mdi - delete
< / v - i c o n >
< / td >
< / template >
< / tr >
<!-- < tr > -- >
<!-- < td colspan = '4' class = 'text-center pa-2' > -- >
<!-- < / td > -- >
<!-- < / tr > -- >
< / tbody >
< / v - s i m p l e - t a b l e >
< div v-if ="!viewMode" class="text-center" >
< v -icon class = "mx-2" small @ click = "addNewColumnRow(table, 'Number')" >
{ { getIcon ( 'Number' ) } }
< / v - i c o n >
< v -icon class = "mx-2" small @ click = "addNewColumnRow(table, 'SingleLineText')" >
{ { getIcon ( 'SingleLineText' ) } }
< / v - i c o n >
< v -icon class = "mx-2" small @ click = "addNewColumnRow(table, 'LongText')" >
{ {
getIcon ( 'LongText' )
} }
< / v - i c o n >
< v -icon class = "mx-2" small @ click = "addNewColumnRow(table, 'LinkToAnotherRecord')" >
{ { getIcon ( 'LinkToAnotherRecord' ) } }
< / v - i c o n >
< v -icon class = "mx-2" small @ click = "addNewColumnRow(table, 'Lookup')" >
{ { getIcon ( 'Lookup' ) } }
< / v - i c o n >
< v -icon class = "mx-2" small @ click = "addNewColumnRow(table, 'Rollup')" >
{ { getIcon ( 'Rollup' ) } }
< / v - i c o n >
< v -btn class = "mx-2" small @click ="addNewColumnRow(table)" >
+ column
< / v - b t n >
< / div >
< / template >
< / v - e x p a n s i o n - p a n e l - c o n t e n t >
< / v - e x p a n s i o n - p a n e l >
< / v - e x p a n s i o n - p a n e l s >
<!-- < v -btn small color = 'primary' class = 'mt-10' @ click = 'createTablesDialog = true' > New Table < / v - b t n > - - >
< div v-if ="!viewMode" class="mx-auto" style="max-width:600px" >
< div class = "mt-10" >
< v -text -field
ref = "project"
v - model = "project.title"
class = "caption"
outlined
dense
label = "Project Name"
: rules = "[v => !!v || 'Project name required'] "
/ >
< / div >
<!--
< div
class = "rounded pa-5 mb-5 d-100 text-center caption"
: style = "{background:project.image_url}"
@ click = "generateGradient"
>
Click to change gradient
< / div > -- >
< gradient -generator v -model = " project.image_url " class = " d-100" / >
< v -row >
< v -col >
< v -text -field
v - model = "project.category"
: rules = "[v => !!v || 'Category name required']"
class = "caption"
outlined
dense
label = "Project Category"
/ >
< / v - c o l >
< v -col >
< v -text -field
v - model = "project.tags"
class = "caption"
outlined
dense
label = "Project Tags"
/ >
< / v - c o l >
< / v - r o w >
< div >
< v -textarea
v - model = "project.description"
class = "caption"
outlined
dense
label = "Project Description"
@ click = "counter++"
/ >
< / div >
< / div >
< / v - c a r d - t e x t >
< / v - c a r d >
< / v - c o l >
< / v - r o w >
< / v - f o r m >
< / v - c o n t a i n e r >
< v -dialog v-model ="createTablesDialog" max-width="500" >
< v -card >
< v -card -title > Enter table name < / v - c a r d - t i t l e >
< v -card -text >
< v -text -field
v - model = "tableNamesInput"
autofocus
hide - details
dense
outlined
label = "Enter comma separated table names"
@ keydown . enter = "addTables"
/ >
< / v - c a r d - t e x t >
< v -card -actions >
< v -spacer / >
< v -btn outlined small @click ="createTablesDialog=false" >
Cancel
< / v - b t n >
< v -btn outlined color = "primary" small @click ="addTables" >
Save
< / v - b t n >
< / v - c a r d - a c t i o n s >
< / v - c a r d >
< / v - d i a l o g >
< v -dialog v-model ="createTableColumnsDialog" max-width="500" >
< v -card >
< v -card -title > Enter column name < / v - c a r d - t i t l e >
< v -card -text >
< v -text -field
v - model = "columnNamesInput"
autofocus
dense
outlined
hide - details
label = "Enter comma separated column names"
@ keydown . enter = "addColumns"
/ >
< / v - c a r d - t e x t >
< v -card -actions >
< v -spacer / >
< v -btn outlined small @click ="createTableColumnsDialog=false" >
Cancel
< / v - b t n >
< v -btn outlined color = "primary" small @click ="addColumns" >
Save
< / v - b t n >
< / v - c a r d - a c t i o n s >
< / v - c a r d >
< / v - d i a l o g >
< help v -model = " helpModal " / >
< v -tooltip v -if = " ! viewMode " left >
< template # activator = "{on}" >
< v -btn
fixed
fab
large
color = "primary"
right
style = "top:45%"
@ click = "createTablesDialog = true"
v - on = "on"
>
< v -icon > mdi - plus < / v - i c o n >
< / v - b t n >
< / template >
< span class = "caption" > Add new table < / span >
< / v - t o o l t i p >
< / div >
< / template >
< script >
import { uiTypes , getUIDTIcon , UITypes } from '~/components/project/spreadsheet/helpers/uiTypes'
import GradientGenerator from '~/components/templates/gradientGenerator'
import Help from '~/components/templates/help'
const LinkToAnotherRecord = 'LinkToAnotherRecord'
const Lookup = 'Lookup'
const Rollup = 'Rollup'
const defaultColProp = { }
export default {
name : 'TemplateEditor' ,
components : { Help , GradientGenerator } ,
props : {
id : [ Number , String ] ,
viewMode : Boolean ,
projectTemplate : Object
} ,
data : ( ) => ( {
loading : false ,
localId : null ,
valid : false ,
url : '' ,
githubConfigForm : false ,
helpModal : false ,
editableTn : { } ,
expansionPanel : 0 ,
project : {
name : 'Project name' ,
tables : [ ]
} ,
tableNamesInput : '' ,
columnNamesInput : '' ,
createTablesDialog : false ,
createTableColumnsDialog : false ,
selectedTable : null ,
uiTypes : uiTypes . filter ( t => ! [ UITypes . Formula , UITypes . SpecificDBType ] . includes ( t . name ) ) ,
rollupFnList : [
{ text : 'count' , value : 'count' } ,
{ text : 'min' , value : 'min' } ,
{ text : 'max' , value : 'max' } ,
{ text : 'avg' , value : 'avg' } ,
{ text : 'min' , value : 'min' } ,
{ text : 'sum' , value : 'sum' } ,
{ text : 'countDistinct' , value : 'countDistinct' } ,
{ text : 'sumDistinct' , value : 'sumDistinct' } ,
{ text : 'avgDistinct' , value : 'avgDistinct' }
] ,
colors : {
LinkToAnotherRecord : 'blue lighten-5' ,
Rollup : 'pink lighten-5' ,
Lookup : 'green lighten-5'
}
} ) ,
computed : {
counter : {
get ( ) {
return this . $store . state . templateC
} ,
set ( c ) {
this . $store . commit ( 'mutTemplateC' , c )
}
} ,
updateFilename ( ) {
return this . url && this . url . split ( '/' ) . pop ( )
}
} ,
watch : {
project : {
deep : true ,
handler ( ) {
const template = {
... this . project ,
tables : ( this . project . tables || [ ] ) . map ( ( t ) => {
const table = { tn : t . tn , columns : [ ] , hasMany : [ ] , manyToMany : [ ] , belongsTo : [ ] , v : [ ] }
for ( const column of ( t . columns || [ ] ) ) {
if ( this . isRelation ( column ) ) {
if ( column . type === 'hm' ) {
table . hasMany . push ( {
tn : column . rtn ,
_cn : column . cn
} )
} else if ( column . type === 'mm' ) {
table . manyToMany . push ( {
rtn : column . rtn ,
_cn : column . cn
} )
} else if ( column . uidt === UITypes . ForeignKey ) {
table . belongsTo . push ( {
tn : column . rtn ,
_cn : column . cn
} )
}
} else if ( this . isLookup ( column ) ) {
if ( column . rtn ) {
table . v . push ( {
_cn : column . cn ,
lk : {
ltn : column . rtn . tn ,
type : column . rtn . type ,
lcn : column . rcn
}
} )
}
} else if ( this . isRollup ( column ) ) {
if ( column . rtn ) {
table . v . push ( {
_cn : column . cn ,
rl : {
rltn : column . rtn . tn ,
rlcn : column . rcn ,
type : column . rtn . type ,
fn : column . fn
}
} )
}
} else {
table . columns . push ( column )
}
}
return table
} )
}
this . $emit ( 'update:projectTemplate' , template )
}
}
} ,
created ( ) {
document . addEventListener ( 'keydown' , this . handleKeyDown )
} ,
destroyed ( ) {
document . removeEventListener ( 'keydown' , this . handleKeyDown )
} ,
mounted ( ) {
if ( this . projectTemplate ) {
this . parseTemplate ( this . projectTemplate )
this . expansionPanel = Array . from ( { length : this . project . tables . length } , ( _ , i ) => i )
}
const input = this . $refs . projec && this . $refs . project . $el . querySelector ( 'input' )
if ( input ) {
input . focus ( )
input . select ( )
}
} ,
methods : {
getIcon ( type ) {
return getUIDTIcon ( type )
} ,
getRelatedTables ( tableName , rollup = false ) {
const tables = [ ]
for ( const t of this . projectTemplate . tables ) {
if ( tableName === t . tn ) {
for ( const hm of t . hasMany ) {
const rTable = this . project . tables . find ( t1 => t1 . tn === hm . tn )
tables . push ( {
... rTable ,
type : 'hm'
} )
}
for ( const mm of t . manyToMany ) {
const rTable = this . project . tables . find ( t1 => t1 . tn === mm . rtn )
tables . push ( {
... rTable ,
type : 'mm'
} )
}
} else {
for ( const hm of t . hasMany ) {
if ( hm . tn === tableName && ! rollup ) {
tables . push ( {
... t ,
type : 'bt'
} )
}
}
for ( const mm of t . manyToMany ) {
if ( mm . rtn === tableName ) {
tables . push ( {
... t ,
type : 'mm'
} )
}
}
}
}
return tables
} ,
validateAndFocus ( ) {
if ( ! this . $refs . form . validate ( ) ) {
const input = this . $el . querySelector ( '.v-input.error--text' )
this . expansionPanel = input && input . parentElement && input . parentElement . parentElement && + input . parentElement . parentElement . dataset . exp
setTimeout ( ( ) => {
input . querySelector ( 'input,select' ) . focus ( )
} , 500 )
return false
}
return true
} ,
deleteTable ( i ) {
const deleteTable = this . project . tables [ i ]
for ( const table of this . project . tables ) {
if ( table === deleteTable ) {
continue
}
table . columns = table . columns . filter ( c => c . rtn !== deleteTable . tn )
}
this . project . tables . splice ( i , 1 )
} ,
deleteTableColumn ( i , j , col , table ) {
const deleteTable = this . project . tables [ i ]
const deleteColumn = deleteTable . columns [ j ]
let rTable , index
// if relation column, delete the corresponding relation from other table
if ( col . uidt === UITypes . LinkToAnotherRecord ) {
if ( col . type === 'hm' ) {
rTable = this . project . tables . find ( t => t . tn === col . rtn )
index = rTable && rTable . columns . findIndex ( c => c . uidt === UITypes . ForeignKey && c . rtn === table . tn )
} else if ( col . type === 'mm' ) {
rTable = this . project . tables . find ( t => t . tn === col . rtn )
index = rTable && rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'mm' )
}
} else if ( col . uidt === UITypes . ForeignKey ) {
rTable = this . project . tables . find ( t => t . tn === col . rtn )
index = rTable && rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'hm' )
}
if ( rTable && index > - 1 ) {
rTable . columns . splice ( index , 1 )
}
for ( const table of this . project . tables ) {
if ( table === deleteTable ) {
continue
}
table . columns = table . columns . filter ( c => c . rtn !== deleteTable . tn || c . rcn !== deleteColumn . cn )
}
deleteTable . columns . splice ( j , 1 )
} ,
addTables ( ) {
if ( ! this . tableNamesInput ) {
return
}
// todo: fix
const re = /(?<=^|,\s*)(\w+)(?:\(((\w+)(?:\s*,\s*\w+)?)?\)){0,1}(?=\s*,|\s*$)/g
let m
// eslint-disable-next-line no-cond-assign
while ( m = re . exec ( this . tableNamesInput ) ) {
if ( this . project . tables . some ( t => t . tn === m [ 1 ] ) ) {
this . $toast . info ( ` Table ' ${ m [ 1 ] } ' is already exist ` ) . goAway ( 1000 )
continue
}
this . project . tables . push ( {
tn : m [ 1 ] ,
columns : ( m [ 2 ] ? m [ 2 ] . split ( /\s*,\s*/ ) : [ ] ) . map ( col => ( {
cn : col ,
... defaultColProp
} ) ) . filter ( ( v , i , arr ) => i === arr . findIndex ( c => c . cn === v . cn ) )
} )
}
this . createTablesDialog = false
this . tableNamesInput = ''
} ,
compareRel ( a , b ) {
return ( ( a && a . tn ) || a ) === ( ( b && b . tn ) || b ) && ( a && a . type ) === ( b && b . type )
} ,
addColumns ( ) {
if ( ! this . columnNamesInput ) {
return
}
const table = this . project . tables [ this . expansionPanel ]
for ( const col of this . columnNamesInput . split ( /\s*,\s*/ ) ) {
if ( table . columns . some ( c => c . cn === col ) ) {
this . $toast . info ( ` Column ' ${ col } ' is already exist ` ) . goAway ( 1000 )
continue
}
table . columns . push ( {
cn : col ,
... defaultColProp
} )
}
this . columnNamesInput = ''
this . createTableColumnsDialog = false
this . $nextTick ( ( ) => {
const input = this . $refs [ ` uidt_ ${ table . tn } _ ${ table . columns . length - 1 } ` ] [ 0 ] . $el . querySelector ( 'input' )
input . focus ( )
this . $nextTick ( ( ) => {
input . select ( )
} )
} )
} ,
showColCreateDialog ( table ) {
this . createTableColumnsDialog = true
this . selectedTable = table
} ,
isRelation ( col ) {
return col . uidt === 'LinkToAnotherRecord' ||
col . uidt === 'ForeignKey'
} ,
isLookup ( col ) {
return col . uidt === 'Lookup'
} ,
isRollup ( col ) {
return col . uidt === 'Rollup'
} ,
isVirtual ( col ) {
return col && uiTypes . some ( ut => ut . name === col . uidt && ut . virtual )
} ,
isLookupOrRollup ( col ) {
return this . isLookup ( col ) ||
this . isRollup ( col )
} ,
isSelect ( col ) {
return col . uidt === 'MultiSelect' ||
col . uidt === 'SingleSelect'
} ,
addNewColumnRow ( table , uidt ) {
table . columns . push ( {
cn : ` title ${ table . columns . length + 1 } ` ,
... defaultColProp ,
uidt ,
... ( uidt === LinkToAnotherRecord
? {
type : 'mm'
}
: { } )
} )
this . $nextTick ( ( ) => {
const input = this . $refs [ ` cn_ ${ table . tn } _ ${ table . columns . length - 1 } ` ] [ 0 ] . $el . querySelector ( 'input' )
input . focus ( )
input . select ( )
} )
} ,
async handleKeyDown ( { metaKey , key , altKey , shiftKey , ctrlKey } ) {
if ( ! ( metaKey && ctrlKey ) && ! ( altKey && shiftKey ) ) {
return
}
switch ( key && key . toLowerCase ( ) ) {
case 't' :
this . createTablesDialog = true
break
case 'c' :
this . createTableColumnsDialog = true
break
case 'a' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] )
break
case 'j' :
this . copyJSON ( )
break
case 's' :
await this . saveTemplate ( )
break
case 'arrowup' :
this . expansionPanel = this . expansionPanel ? -- this . expansionPanel : this . project . tables . length - 1
break
case 'arrowdown' :
this . expansionPanel = ++ this . expansionPanel % this . project . tables . length
break
case '1' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] , 'Number' )
break
case '2' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] , 'SingleLineText' )
break
case '3' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] , 'LongText' )
break
case '4' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] , 'LinkToAnotherRecord' )
break
case '5' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] , 'Lookup' )
break
case '6' :
this . addNewColumnRow ( this . project . tables [ this . expansionPanel ] , 'Rollup' )
break
}
} ,
copyJSON ( ) {
if ( ! this . validateAndFocus ( ) ) {
this . $toast . info ( 'Please fill all the required column!' ) . goAway ( 5000 )
return
}
const el = document . createElement ( 'textarea' )
el . addEventListener ( 'focusin' , e => e . stopPropagation ( ) )
el . value = JSON . stringify ( this . projectTemplate , null , 2 )
el . style = { position : 'absolute' , left : '-9999px' }
document . body . appendChild ( el )
el . select ( )
document . execCommand ( 'copy' )
document . body . removeChild ( el )
this . $toast . success ( 'Successfully copied JSON data to clipboard!' ) . goAway ( 3000 )
return true
} ,
openUrl ( ) {
window . open ( this . url , '_blank' )
} ,
async loadUrl ( ) {
try {
let template = ( await this . $axios . get ( this . url ) ) . data
if ( typeof template === 'string' ) {
template = JSON . parse ( template )
}
console . log ( template )
this . parseTemplate ( template )
} catch ( e ) {
this . $toast . error ( e . message ) . goAway ( 5000 )
}
} ,
parseTemplate ( { tables = [ ] , ... rest } ) {
const parsedTemplate = {
... rest ,
tables : tables . map ( ( { manyToMany = [ ] , hasMany = [ ] , belongsTo = [ ] , v = [ ] , columns = [ ] , ... rest } ) => ( {
... rest ,
columns : [
... columns ,
... manyToMany . map ( mm => ( {
cn : mm . _cn || ` ${ rest . tn } <=> ${ mm . rtn } ` ,
uidt : LinkToAnotherRecord ,
type : 'mm' ,
... mm
} ) ) ,
... hasMany . map ( hm => ( {
cn : hm . _cn || ` ${ rest . tn } => ${ hm . tn } ` ,
uidt : LinkToAnotherRecord ,
type : 'hm' ,
rtn : hm . tn ,
... hm
} ) ) ,
... belongsTo . map ( bt => ( {
cn : bt . _cn || ` ${ rest . tn } => ${ bt . rtn } ` ,
uidt : UITypes . ForeignKey ,
rtn : bt . tn ,
... bt
} ) ) ,
... v . map ( ( v ) => {
const res = {
cn : v . _cn ,
rtn : {
... v
}
}
if ( v . lk ) {
res . uidt = Lookup
res . rtn . tn = v . lk . ltn
res . rcn = v . lk . lcn
res . rtn . type = v . lk . type
} else if ( v . rl ) {
res . uidt = Rollup
res . rtn . tn = v . rl . rltn
res . rcn = v . rl . rlcn
res . rtn . type = v . rl . type
res . fn = v . rl . fn
}
return res
} )
]
} ) )
}
this . project = parsedTemplate
} ,
async projectTemplateCreate ( ) {
if ( ! this . validateAndFocus ( ) ) {
this . $toast . info ( 'Please fill all the required column!' ) . goAway ( 5000 )
return
}
try {
const githubConfig = this . $store . state . github
// const token = await models.store.where({ key: 'GITHUB_TOKEN' }).first()
// const branch = await models.store.where({ key: 'GITHUB_BRANCH' }).first()
// const filePath = await models.store.where({ key: 'GITHUB_FILE_PATH' }).first()
// const templateRepo = await models.store.where({ key: 'PROJECT_TEMPLATES_REPO' }).first()
if ( ! githubConfig . token || ! githubConfig . repo ) {
throw new Error ( 'Missing token or template path' )
}
const data = JSON . stringify ( this . projectTemplate , 0 , 2 )
const filename = this . updateFilename || ` ${ this . projectTemplate . name } _ ${ Date . now ( ) } .json `
const filePath = ` ${ githubConfig . filePath ? githubConfig . filePath + '/' : '' } ${ filename } `
const apiPath = ` https://api.github.com/repos/ ${ githubConfig . repo } /contents/ ${ filePath } `
let sha
if ( this . updateFilename ) {
const { data : { sha : _sha } } = await this . $axios ( {
url : ` https://api.github.com/repos/ ${ githubConfig . repo } /contents/ ${ filePath } ` ,
method : 'get' ,
headers : {
Authorization : 'token ' + githubConfig . token
}
} )
sha = _sha
}
await this . $axios ( {
url : apiPath ,
method : 'put' ,
headers : {
'Content-Type' : 'application/json' ,
Authorization : 'token ' + githubConfig . token
} ,
data : {
message : ` templates : init template ${ filename } ` ,
content : Base64 . encode ( data ) ,
sha ,
branch : githubConfig . branch
}
} )
this . url = ` https://raw.githubusercontent.com/ ${ githubConfig . repo } / ${ githubConfig . branch } / ${ filePath } `
this . $toast . success ( 'Template generated and saved successfully!' ) . goAway ( 4000 )
} catch ( e ) {
this . $toast . error ( e . message ) . goAway ( 5000 )
}
} ,
navigateToTable ( tn ) {
const index = this . projectTemplate . tables . findIndex ( t => t . tn === tn )
if ( Array . isArray ( this . expansionPanel ) ) {
this . expansionPanel . push ( index )
} else {
this . expansionPanel = index
}
this . $nextTick ( ( ) => {
const accord = this . $el . querySelector ( ` #tn_ ${ tn } ` )
accord . focus ( )
accord . scrollIntoView ( )
} )
} ,
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 , {
params : {
token : this . $store . state . template
}
} )
this . $toast . success ( 'Template updated successfully' ) . goAway ( 3000 )
} else if ( ! this . $store . state . template ) {
if ( ! this . copyJSON ( ) ) {
return
}
this . $toast . info ( 'Initiating Github for template' ) . goAway ( 3000 )
const res = await this . $axios . post ( ` ${ process . env . NC _API _URL } /api/v1/projectTemplateCreate ` , this . projectTemplate )
console . log ( res )
this . $toast . success ( 'Initiated Github successfully' ) . goAway ( 3000 )
window . open ( res . data . path , '_blank' )
} else {
const res = await this . $axios . post ( ` ${ process . env . NC _API _URL } /api/v1/nc/templates ` , this . projectTemplate , {
params : {
token : this . $store . state . template
}
} )
this . localId = res . data . id
this . $toast . success ( 'Template updated successfully' ) . goAway ( 3000 )
}
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 )
}
}
}
}
} ,
async onRtnChange ( oldVal , newVal , col , table ) {
this . $set ( col , 'rtn' , newVal )
await this . $nextTick ( )
if ( col . uidt !== UITypes . LinkToAnotherRecord && col . uidt !== UITypes . ForeignKey ) {
return
}
if ( oldVal ) {
const rTable = this . project . tables . find ( t => t . tn === oldVal )
// delete relation from other table if exist
let index = - 1
if ( col . uidt === UITypes . LinkToAnotherRecord && col . type === 'mm' ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'mm' )
} else if ( col . uidt === UITypes . LinkToAnotherRecord && col . type === 'hm' ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . ForeignKey && c . rtn === table . tn )
} else if ( col . uidt === UITypes . ForeignKey ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'hm' )
}
if ( index > - 1 ) {
rTable . columns . splice ( index , 1 )
}
}
if ( newVal ) {
const rTable = this . project . tables . find ( t => t . tn === newVal )
// check relation relation exist in other table
// if not create a relation
if ( col . uidt === UITypes . LinkToAnotherRecord && col . type === 'mm' ) {
if ( ! rTable . columns . find ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'mm' ) ) {
rTable . columns . push ( {
cn : ` title ${ rTable . columns . length + 1 } ` ,
uidt : UITypes . LinkToAnotherRecord ,
type : 'mm' ,
rtn : table . tn
} )
}
} else if ( col . uidt === UITypes . LinkToAnotherRecord && col . type === 'hm' ) {
if ( ! rTable . columns . find ( c => c . uidt === UITypes . ForeignKey && c . rtn === table . tn ) ) {
rTable . columns . push ( {
cn : ` title ${ rTable . columns . length + 1 } ` ,
uidt : UITypes . ForeignKey ,
rtn : table . tn
} )
}
} else if ( col . uidt === UITypes . ForeignKey ) {
if ( ! rTable . columns . find ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'hm' ) ) {
rTable . columns . push ( {
cn : ` title ${ rTable . columns . length + 1 } ` ,
uidt : UITypes . LinkToAnotherRecord ,
type : 'hm' ,
rtn : table . tn
} )
}
}
}
} ,
onRTypeChange ( oldType , newType , col , table ) {
this . $set ( col , 'type' , newType )
const rTable = this . project . tables . find ( t => t . tn === col . rtn )
let index = - 1
// find column and update relation
// or create a new column
if ( oldType === 'hm' ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . ForeignKey && c . rtn === table . tn )
} else if ( oldType === 'mm' ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'mm' )
}
const rCol = index === - 1 ? { cn : ` title ${ rTable . columns . length + 1 } ` } : { ... rTable . columns [ index ] }
index = index === - 1 ? rTable . columns . length : index
if ( newType === 'mm' ) {
rCol . type = 'mm'
rCol . uidt = UITypes . LinkToAnotherRecord
} else if ( newType === 'hm' ) {
rCol . type = 'bt'
rCol . uidt = UITypes . ForeignKey
}
rCol . rtn = table . tn
this . $set ( rTable . columns , index , rCol )
} ,
onUidtChange ( oldVal , newVal , col , table ) {
this . $set ( col , 'uidt' , newVal )
// delete relation column from other table
// if previous type is relation
let index = - 1
let rTable
if ( oldVal === UITypes . LinkToAnotherRecord ) {
rTable = this . project . tables . find ( t => t . tn === col . rtn )
if ( col . type === 'hm' ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . ForeignKey && c . rtn === table . tn )
} else if ( col . type === 'mm' ) {
index = rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'mm' )
}
} else if ( oldVal === UITypes . ForeignKey ) {
rTable = this . project . tables . find ( t => t . tn === col . rtn )
index = rTable . columns . findIndex ( c => c . uidt === UITypes . LinkToAnotherRecord && c . rtn === table . tn && c . type === 'hm' )
}
if ( rTable && index > - 1 ) {
rTable . columns . splice ( index , 1 )
}
col . rtn = undefined
col . type = undefined
col . rcn = undefined
if ( col . uidt === LinkToAnotherRecord ) {
col . type = col . type || 'mm'
}
}
}
}
< / script >
< style scoped >
< / style >