mirror of https://github.com/nocodb/nocodb
Browse Source
# Conflicts: # packages/nocodb/package-lock.json # packages/nocodb/package.jsonpull/341/head
Pranav C
3 years ago
128 changed files with 6020 additions and 2516 deletions
@ -1,76 +0,0 @@
|
||||
<template> |
||||
<v-menu> |
||||
<template v-slot:activator="{on}"> |
||||
<div class="value" v-on="on">{{ localState }}</div> |
||||
</template> |
||||
<v-date-picker v-on="parentListeners" v-model="localState"></v-date-picker> |
||||
</v-menu> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "date-picker-cell", props: { |
||||
value: [String, Date] |
||||
}, |
||||
mounted() { |
||||
if (this.$el && this.$el.$el) { |
||||
this.$el.$el.focus(); |
||||
} |
||||
}, |
||||
computed: { |
||||
localState: { |
||||
get() { |
||||
return typeof this.value === 'string' ? this.value.replace(/(\d)T(?=\d)/, '$1 ') : this.value; |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', new Date(val).toJSON().slice(0, 10)); |
||||
} |
||||
}, |
||||
parentListeners() { |
||||
const $listeners = {}; |
||||
|
||||
if (this.$listeners.blur) { |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if (this.$listeners.focus) { |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
.value { |
||||
width: 100%; |
||||
height: 100%; |
||||
min-height:20px; |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,93 +0,0 @@
|
||||
<template> |
||||
<v-datetime-picker |
||||
v-on="parentListeners" |
||||
class="caption xc-date-time-picker" |
||||
ref="picker" |
||||
:text-field-props="{ |
||||
class:'caption mt-n1 pt-0' |
||||
}" |
||||
:time-picker-props="{ |
||||
format:'24hr' |
||||
}" |
||||
v-model="localState" |
||||
></v-datetime-picker> |
||||
</template> |
||||
|
||||
<script> |
||||
|
||||
export default { |
||||
name: "date-time-picker-cell", |
||||
props: ['value', 'ignoreFocus'], |
||||
mounted() { |
||||
if (!this.ignoreFocus) { |
||||
this.$refs.picker.display = true; |
||||
} |
||||
}, |
||||
computed: { |
||||
localState: { |
||||
get() { |
||||
if(/^\d{6,}$/.test(this.value)){ |
||||
return new Date(+this.value); |
||||
} |
||||
|
||||
|
||||
return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : this.value; |
||||
}, |
||||
set(val) { |
||||
// if(/^\d{6,}$/.test(this.value)){ |
||||
// return this.$emit('input', new Date(this.value).getTime()); |
||||
// } |
||||
|
||||
|
||||
const uVal = new Date(val).toISOString().slice(0, 19).replace('T', ' ').replace(/(\d{1,2}:\d{1,2}):\d{1,2}$/,'$1'); |
||||
console.log(val, uVal) |
||||
this.$emit('input', uVal); |
||||
} |
||||
}, |
||||
parentListeners(){ |
||||
const $listeners = {}; |
||||
|
||||
if(this.$listeners.blur){ |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if(this.$listeners.focus){ |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
/deep/ .v-input, /deep/ .v-text-field { |
||||
margin-top: 0 !important; |
||||
padding-top: 0 !important; |
||||
font-size: inherit !important; |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,89 +0,0 @@
|
||||
<template> |
||||
<select v-on="parentListeners" v-model="localState"> |
||||
<option v-for="eVal of enumValues" :key="eVal" :value="eVal">{{ eVal }}</option> |
||||
</select> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "enum-list-cell", |
||||
props: { |
||||
value: String, |
||||
column: Object |
||||
}, |
||||
mounted() { |
||||
this.$el.focus(); |
||||
let event; |
||||
event = document.createEvent('MouseEvents'); |
||||
event.initMouseEvent('mousedown', true, true, window); |
||||
this.$el.dispatchEvent(event); |
||||
}, |
||||
computed: { |
||||
localState: { |
||||
get() { |
||||
return this.value |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', val); |
||||
this.$emit('update'); |
||||
} |
||||
}, |
||||
enumValues() { |
||||
if (this.column && this.column.dtxp) { |
||||
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, '')) |
||||
} |
||||
return []; |
||||
}, |
||||
parentListeners(){ |
||||
const $listeners = {}; |
||||
|
||||
if(this.$listeners.blur){ |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if(this.$listeners.focus){ |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
select { |
||||
width: 100%; |
||||
height: 100%; |
||||
color: var(--v-textColor-base); |
||||
-webkit-appearance: menulist; |
||||
/*webkit browsers */ |
||||
-moz-appearance: menulist; |
||||
/*Firefox */ |
||||
appearance: menulist; |
||||
} |
||||
|
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,100 +0,0 @@
|
||||
<template> |
||||
<div class="d-flex align-center"> |
||||
|
||||
<div> |
||||
<div class="" v-for="(val,i) of setValues" :key="val"> |
||||
<input type="checkbox" :id="`key-check-box-${val}`" class="orange--text" v-model="localState" :value="val"> |
||||
<label class="py-1 px-3 d-inline-block my-1 label" :for="`key-check-box-${val}`" |
||||
:style="{ |
||||
background:colors[i % colors.length ] |
||||
}" |
||||
>{{ val }}</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import colors from "@/components/project/spreadsheet/helpers/colors"; |
||||
|
||||
export default { |
||||
name: "set-list-checkbox-cell", |
||||
props: { |
||||
value: String, |
||||
column: Object |
||||
}, |
||||
data() { |
||||
}, |
||||
mounted() { |
||||
this.$el.focus(); |
||||
let event; |
||||
event = document.createEvent('MouseEvents'); |
||||
event.initMouseEvent('mousedown', true, true, window); |
||||
this.$el.dispatchEvent(event); |
||||
}, |
||||
computed: { |
||||
colors() { |
||||
return this.$store.state.windows.darkTheme ? colors.dark : colors.light; |
||||
}, |
||||
localState: { |
||||
get() { |
||||
return this.value && this.value.split(',') |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', val.join(',')); |
||||
this.$emit('update'); |
||||
} |
||||
}, |
||||
setValues() { |
||||
if (this.column && this.column.dtxp) { |
||||
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, '')) |
||||
} |
||||
return []; |
||||
}, |
||||
parentListeners() { |
||||
const $listeners = {}; |
||||
|
||||
if (this.$listeners.blur) { |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if (this.$listeners.focus) { |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
.label { |
||||
border-radius: 25px; |
||||
} |
||||
|
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,91 +0,0 @@
|
||||
<template> |
||||
<div> |
||||
<select v-on="parentListeners" v-model="localState" multiple> |
||||
<option v-for="val of setValues" :key="val" :value="val">{{ val }}</option> |
||||
</select> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "set-list-cell", |
||||
props: { |
||||
value: String, |
||||
column: Object |
||||
}, |
||||
mounted() { |
||||
this.$el.focus(); |
||||
let event; |
||||
event = document.createEvent('MouseEvents'); |
||||
event.initMouseEvent('mousedown', true, true, window); |
||||
this.$el.dispatchEvent(event); |
||||
}, |
||||
computed: { |
||||
localState: { |
||||
get() { |
||||
return this.value && this.value.split(',') |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', val.join(',')); |
||||
this.$emit('update'); |
||||
} |
||||
}, |
||||
setValues() { |
||||
if (this.column && this.column.dtxp) { |
||||
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, '')) |
||||
} |
||||
return []; |
||||
}, |
||||
parentListeners(){ |
||||
const $listeners = {}; |
||||
|
||||
if(this.$listeners.blur){ |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if(this.$listeners.focus){ |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
select { |
||||
width: 100%; |
||||
height: 100%; |
||||
color: var(--v-textColor-base); |
||||
-webkit-appearance: menulist; |
||||
/*webkit browsers */ |
||||
-moz-appearance: menulist; |
||||
/*Firefox */ |
||||
appearance: menulist; |
||||
} |
||||
|
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,78 +0,0 @@
|
||||
<template> |
||||
<v-menu> |
||||
<template v-slot:activator="{on}"> |
||||
<div class="value" v-on="on">{{ localState }}</div> |
||||
</template> |
||||
<v-time-picker v-on="parentListeners" v-model="localState"></v-time-picker> |
||||
</v-menu> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "time-picker-cell", |
||||
props: { |
||||
value: [String, Date] |
||||
}, |
||||
mounted() { |
||||
if (this.$el && this.$el.$el) { |
||||
this.$el.$el.focus(); |
||||
} |
||||
}, |
||||
computed: { |
||||
localState: { |
||||
get() { |
||||
return typeof this.value === 'string' ? this.value.replace(/(\d)T(?=\d)/, '$1 ') : this.value; |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', (new Date(val).toJSON() || '').slice(0, 10)); |
||||
} |
||||
}, |
||||
parentListeners() { |
||||
const $listeners = {}; |
||||
|
||||
if (this.$listeners.blur) { |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if (this.$listeners.focus) { |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
if (this.$listeners.cancel) { |
||||
$listeners.cancel = this.$listeners.cancel; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.value { |
||||
min-height: 20px; |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,26 +1,28 @@
|
||||
<template> |
||||
<editable-attachment-cell |
||||
:isLocked="isLocked" |
||||
:db-alias="dbAlias" |
||||
@click.stop="$emit('enableedit')" v-if="isAttachment" :value="value" :column="column"></editable-attachment-cell> |
||||
<set-list-cell @click.stop="$emit('enableedit')" v-else-if="isSet" :value="value" :column="column"></set-list-cell> |
||||
<!-- <enum-list-editable-cell @click.stop="$emit('enableedit')" v-else-if="isEnum && selected" :value="value" :column="column"></enum-list-editable-cell>--> |
||||
<enum-cell @click.stop="$emit('enableedit')" v-else-if="isEnum" :value="value" :column="column"></enum-cell> |
||||
<span v-else>{{ value }}</span> |
||||
<v-lazy> |
||||
<editable-attachment-cell |
||||
:isLocked="isLocked" |
||||
:db-alias="dbAlias" |
||||
@click.stop="$emit('enableedit')" v-if="isAttachment" :value="value" :column="column"></editable-attachment-cell> |
||||
<set-list-cell @click.stop="$emit('enableedit')" v-else-if="isSet" :value="value" :column="column"></set-list-cell> |
||||
<!-- <enum-list-editable-cell @click.stop="$emit('enableedit')" v-else-if="isEnum && selected" :value="value" :column="column"></enum-list-editable-cell>--> |
||||
<enum-cell @click.stop="$emit('enableedit')" v-else-if="isEnum" :value="value" :column="column"></enum-cell> |
||||
<span v-else>{{ value }}</span> |
||||
</v-lazy> |
||||
</template> |
||||
|
||||
<script> |
||||
import cell from "@/components/project/spreadsheet/mixins/cell"; |
||||
import SetListCell from "@/components/project/spreadsheet/cell/setListCell"; |
||||
import EnumCell from "@/components/project/spreadsheet/cell/enumCell"; |
||||
import AttachmentCell from "@/components/project/spreadsheet/cell/attachmentCell"; |
||||
import EditableAttachmentCell from "@/components/project/spreadsheet/editableCell/editableAttachmentCell"; |
||||
import EnumListEditableCell from "@/components/project/spreadsheet/editableCell/enumListEditableCell"; |
||||
import SetListCell from "@/components/project/spreadsheet/components/cell/setListCell"; |
||||
import EnumCell from "@/components/project/spreadsheet/components/cell/enumCell"; |
||||
import AttachmentCell from "@/components/project/spreadsheet/components/cell/attachmentCell"; |
||||
import EditableAttachmentCell from "@/components/project/spreadsheet/components/editableCell/editableAttachmentCell"; |
||||
import EnumListEditableCell from "@/components/project/spreadsheet/components/editableCell/enumListEditableCell"; |
||||
|
||||
export default { |
||||
name: "tableCell", |
||||
components: {EnumListEditableCell, EditableAttachmentCell, AttachmentCell, EnumCell, SetListCell}, |
||||
props: ['value','dbAlias','isLocked','selected'], |
||||
props: ['value', 'dbAlias', 'isLocked', 'selected'], |
||||
mixins: [cell], |
||||
computed: {} |
||||
} |
@ -1,38 +1,43 @@
|
||||
<template> |
||||
<div> |
||||
<span v-for="v in (value || '').split(',')" :key="v" :style="{ |
||||
background:colors[v] |
||||
}" class="set-item ma-1 py-1 px-3">{{ v }}</span> |
||||
<v-chip |
||||
small |
||||
v-for="v in (value || '').split(',')" |
||||
:key="v" |
||||
:color="colors[setValues.indexOf(v) % colors.length]" |
||||
class="set-item ma-1 py-1 px-3" |
||||
> |
||||
{{ v }} |
||||
</v-chip> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import colors from "@/components/project/spreadsheet/helpers/colors"; |
||||
import colors from "@/mixins/colors"; |
||||
|
||||
export default { |
||||
props: ['value', 'column'], |
||||
name: "setListCell", |
||||
mixins: [colors], |
||||
computed: { |
||||
colors() { |
||||
const col = this.$store.state.windows.darkTheme ? colors.dark : colors.light; |
||||
|
||||
setValues() { |
||||
if (this.column && this.column.dtxp) { |
||||
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, '')).reduce((obj, v, i) => ({ |
||||
...obj, |
||||
[v]: col[i] |
||||
}), {}) |
||||
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, '')) |
||||
} |
||||
return {}; |
||||
} |
||||
return []; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
/* |
||||
.set-item { |
||||
display: inline-block; |
||||
border-radius: 25px; |
||||
white-space: nowrap; |
||||
} |
||||
}*/ |
||||
</style> |
||||
<!-- |
||||
/** |
@ -0,0 +1,302 @@
|
||||
<template> |
||||
<div> |
||||
<v-container fluid class="wrapper mb-3"> |
||||
<v-row> |
||||
<v-col> |
||||
<v-radio-group row hide-details dense v-model="type" @change="$refs.input.validate()" class="pt-0 mt-0"> |
||||
<v-radio value="hm" label="Has Many"></v-radio> |
||||
<v-radio value="mm" label="Many To Many"></v-radio> |
||||
<v-radio disabled value="oo" label="One To One"></v-radio> |
||||
</v-radio-group> |
||||
</v-col> |
||||
</v-row> |
||||
</v-container> |
||||
|
||||
<v-container fluid class="wrapper"> |
||||
<v-row> |
||||
<v-col cols="12"> |
||||
<v-autocomplete |
||||
ref="input" |
||||
outlined |
||||
class="caption" |
||||
hide-details="auto" |
||||
:loading="isRefTablesLoading" |
||||
label="Child Table" |
||||
:full-width="false" |
||||
v-model="relation.childTable" |
||||
:items="refTables" |
||||
item-text="_tn" |
||||
item-value="tn" |
||||
required |
||||
dense |
||||
:rules="tableRules" |
||||
></v-autocomplete> |
||||
</v-col |
||||
> |
||||
<!-- <v-col cols="6"> |
||||
<v-text-field |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
label="Child Column" |
||||
:full-width="false" |
||||
v-model="relation.childColumn" |
||||
required |
||||
dense |
||||
ref="childColumnRef" |
||||
@change="onColumnSelect" |
||||
></v-text-field> |
||||
</v-col |
||||
>--> |
||||
</v-row> |
||||
<template v-if="!isSQLite"> |
||||
<v-row> |
||||
<v-col cols="6"> |
||||
<v-autocomplete |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
label="On Update" |
||||
:full-width="false" |
||||
v-model="relation.onUpdate" |
||||
:items="onUpdateDeleteOptions" |
||||
required |
||||
dense |
||||
:disabled="relation.type !== 'real'" |
||||
></v-autocomplete> |
||||
</v-col> |
||||
<v-col cols="6"> |
||||
<v-autocomplete |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
label="On Delete" |
||||
:full-width="false" |
||||
v-model="relation.onDelete" |
||||
:items="onUpdateDeleteOptions" |
||||
required |
||||
dense |
||||
:disabled="relation.type !== 'real'" |
||||
></v-autocomplete> |
||||
</v-col> |
||||
</v-row> |
||||
|
||||
|
||||
<v-row> |
||||
|
||||
<v-col> |
||||
<v-checkbox |
||||
false-value="real" |
||||
true-value="virtual" |
||||
label="Virtual Relation" |
||||
:full-width="false" |
||||
v-model="relation.type" |
||||
required |
||||
class="mt-0" |
||||
dense |
||||
></v-checkbox> |
||||
</v-col> |
||||
</v-row> |
||||
</template> |
||||
</v-container> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "linked-to-another-options", |
||||
props: ['nodes', 'column', 'meta', 'isSQLite', 'alias'], |
||||
data: () => ({ |
||||
type: 'hm', |
||||
refTables: [], |
||||
refColumns: [], |
||||
relation: {}, |
||||
isRefTablesLoading: false, |
||||
isRefColumnsLoading: false, |
||||
}), |
||||
async created() { |
||||
await this.loadTablesList(); |
||||
this.relation = { |
||||
childColumn: `${this.meta.tn}_id`, |
||||
childTable: this.nodes.tn, |
||||
parentTable: this.column.rtn || "", |
||||
parentColumn: this.column.rcn || "", |
||||
onDelete: "NO ACTION", |
||||
onUpdate: "NO ACTION", |
||||
updateRelation: this.column.rtn ? true : false, |
||||
type: 'real' |
||||
} |
||||
}, |
||||
computed: { |
||||
onUpdateDeleteOptions() { |
||||
if (this.isMSSQL) { |
||||
return ["NO ACTION"] |
||||
} |
||||
return [ |
||||
"NO ACTION", |
||||
"CASCADE", |
||||
"RESTRICT", |
||||
"SET NULL", |
||||
"SET DEFAULT" |
||||
]; |
||||
}, |
||||
tableRules() { |
||||
return [ |
||||
v => !!v || 'Required', |
||||
v => { |
||||
if (this.type === 'mm') |
||||
return !(this.meta.manyToMany || []) |
||||
.some(mm => mm.tn === v && mm.rtn === this.meta.tn || mm.rtn === v && mm.tn === this.meta.tn) |
||||
|| 'Duplicate many to many relation is not allowed at the moment'; |
||||
if (this.type === 'hm') |
||||
return !(this.meta.hasMany || []) |
||||
.some(hm => hm.tn === v) |
||||
|| 'Duplicate has many relation is not allowed at the moment'; |
||||
}, |
||||
] |
||||
} |
||||
}, |
||||
methods: { |
||||
async loadColumnList() { |
||||
this.isRefColumnsLoading = true; |
||||
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, 'columnList', {tn: this.meta.tn}]) |
||||
|
||||
|
||||
const columns = result.data.list; |
||||
this.refColumns = JSON.parse(JSON.stringify(columns)); |
||||
|
||||
if (this.relation.updateRelation && !this.relationColumnChanged) { |
||||
//only first time when editing add defaault value to this field |
||||
this.relation.parentColumn = this.column.rcn; |
||||
this.relationColumnChanged = true; |
||||
} else { |
||||
//find pk column and assign to parentColumn |
||||
const pkKeyColumns = this.refColumns.filter(el => el.pk); |
||||
this.relation.parentColumn = (pkKeyColumns[0] || {}).cn || ""; |
||||
} |
||||
this.onColumnSelect(); |
||||
|
||||
this.isRefColumnsLoading = false; |
||||
}, |
||||
async loadTablesList() { |
||||
this.isRefTablesLoading = true; |
||||
|
||||
|
||||
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, 'tableList']); |
||||
|
||||
|
||||
this.refTables = result.data.list.map(({tn, _tn}) => ({tn, _tn})) |
||||
this.isRefTablesLoading = false; |
||||
}, |
||||
async saveManyToMany() { |
||||
try { |
||||
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [ |
||||
{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, |
||||
'xcM2MRelationCreate', |
||||
{ |
||||
_cn: this.alias, |
||||
...this.relation, |
||||
type: this.isSQLite || this.relation.type === 'virtual' ? 'virtual' : 'real', |
||||
parentTable: this.meta.tn, |
||||
updateRelation: this.column.rtn ? true : false |
||||
} |
||||
]); |
||||
} catch (e) { |
||||
throw e |
||||
} |
||||
}, |
||||
async saveRelation() { |
||||
if (this.type === 'mm') { |
||||
await this.saveManyToMany(); |
||||
return; |
||||
} |
||||
try { |
||||
const parentPK = this.meta.columns.find(c => c.pk); |
||||
|
||||
|
||||
const childTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, 'tableXcModelGet', { |
||||
tn: this.relation.childTable |
||||
}]); |
||||
|
||||
const childMeta = JSON.parse(childTableData.meta) |
||||
|
||||
const newChildColumn = {}; |
||||
|
||||
|
||||
Object.assign(newChildColumn, { |
||||
cn: this.relation.childColumn, |
||||
_cn: this.relation.childColumn, |
||||
rqd: false, |
||||
pk: false, |
||||
ai: false, |
||||
cdf: null, |
||||
dt: parentPK.dt, |
||||
dtxp: parentPK.dtxp, |
||||
dtxs: parentPK.dtxs, |
||||
un: parentPK.un, |
||||
altered: 1 |
||||
}); |
||||
|
||||
const columns = [...childMeta.columns, newChildColumn]; |
||||
|
||||
let result = await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, "tableUpdate", { |
||||
tn: childMeta.tn, |
||||
_tn: childMeta._tn, |
||||
originalColumns: childMeta.columns, |
||||
columns |
||||
}]); |
||||
|
||||
|
||||
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [ |
||||
{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, |
||||
this.relation.type === 'real' && !this.isSQLite ? "relationCreate" : 'xcVirtualRelationCreate', |
||||
{ |
||||
...this.relation, |
||||
parentTable: this.meta.tn, |
||||
parentColumn: parentPK.cn, |
||||
updateRelation: this.column.rtn ? true : false, |
||||
type: 'real' |
||||
} |
||||
]); |
||||
} catch (e) { |
||||
throw e |
||||
} |
||||
}, |
||||
onColumnSelect() { |
||||
const col = this.refColumns.find(c => this.relation.parentColumn === c.cn); |
||||
this.$emit('onColumnSelect', col) |
||||
} |
||||
}, |
||||
|
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
.wrapper { |
||||
border: solid 2px #7f828b33; |
||||
border-radius: 4px; |
||||
} |
||||
|
||||
/deep/ .v-input__append-inner { |
||||
margin-top: 4px !important; |
||||
} |
||||
</style> |
@ -0,0 +1,131 @@
|
||||
<template> |
||||
<v-card min-width="300px" max-width="400px" max-height="95vh" style="overflow: auto" |
||||
class="elevation-0 card"> |
||||
<v-form v-model="valid"> |
||||
<v-container fluid @click.stop.prevent> |
||||
<v-row> |
||||
<v-col cols="12" class="d-flex pb-0"> |
||||
|
||||
<v-spacer></v-spacer> |
||||
<v-btn x-small outlined @click="close">Cancel</v-btn> |
||||
<v-btn x-small color="primary" @click="save" :disabled="!valid">Save</v-btn> |
||||
</v-col> |
||||
<v-col cols="12"> |
||||
<v-text-field |
||||
ref="column" |
||||
hide-details="auto" |
||||
color="primary" |
||||
v-model="newColumn._cn" |
||||
class="caption" |
||||
label="Column name" |
||||
:rules="[ |
||||
v => !!v || 'Required', |
||||
v => !meta || !meta.columns || !column ||meta.columns.every(c => v !== c.cn ) && meta.v.every(c => column && c._cn === column._cn || v !== c._cn ) || 'Duplicate column name' |
||||
]" |
||||
dense outlined></v-text-field> |
||||
</v-col> |
||||
|
||||
|
||||
</v-row> |
||||
</v-container> |
||||
</v-form> |
||||
|
||||
|
||||
</v-card> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "editVirtualColumn", |
||||
components: {}, |
||||
props: { |
||||
nodes: Object, |
||||
meta: Object, |
||||
value: Boolean, |
||||
column: Object |
||||
}, |
||||
data: () => ({ |
||||
valid: false, |
||||
newColumn: {} |
||||
}), |
||||
async created() { |
||||
}, |
||||
methods: { |
||||
close() { |
||||
this.$emit('input', false); |
||||
this.newColumn = {}; |
||||
}, |
||||
async save() { |
||||
try { |
||||
await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, 'xcUpdateVirtualKeyAlias', { |
||||
tn: this.nodes.tn, |
||||
oldAlias: this.column._cn, |
||||
newAlias: this.newColumn._cn, |
||||
}]); |
||||
|
||||
this.$toast.success('Successfully updated alias').goAway(3000); |
||||
} catch (e) { |
||||
console.log(e) |
||||
this.$toast.error('Failed to update column alias').goAway(3000); |
||||
} |
||||
this.$emit('saved'); |
||||
this.$emit('input', false); |
||||
}, |
||||
|
||||
focusInput() { |
||||
setTimeout(() => { |
||||
if (this.$refs.column && this.$refs.column.$el) { |
||||
this.$refs.column.$el.querySelector('input').focus() |
||||
} |
||||
}, 100); |
||||
}, |
||||
|
||||
}, mounted() { |
||||
this.newColumn = {...this.column} |
||||
}, watch: { |
||||
column(c) { |
||||
this.newColumn = {...c} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
|
||||
::v-deep { |
||||
|
||||
|
||||
.v-input__slot { |
||||
min-height: auto !important; |
||||
} |
||||
|
||||
.v-input:not(.v-input--is-focused) fieldset { |
||||
border-color: #7f828b33 !important; |
||||
} |
||||
|
||||
.ui-type input { |
||||
height: 24px; |
||||
} |
||||
|
||||
.v-input--selection-controls__input > i { |
||||
transform: scale(.83); |
||||
} |
||||
|
||||
label { |
||||
font-size: 0.75rem !important |
||||
} |
||||
|
||||
.v-text-field--outlined.v-input--dense .v-label:not(.v-label--active) { |
||||
top: 6px; |
||||
} |
||||
} |
||||
|
||||
.card { |
||||
border: solid 2px #7f828b33; |
||||
} |
||||
|
||||
</style> |
@ -1,11 +1,11 @@
|
||||
<template> |
||||
<v-select v-on="parentListeners" v-model="localState" dense flat :items="enumValues" hide-details class="mt-0" :clearable="!column.rqd"> |
||||
<v-select solo v-on="parentListeners" v-model="localState" dense flat :items="enumValues" hide-details class="mt-0" :clearable="!column.rqd"> |
||||
<!-- <option v-for="eVal of enumValues" :key="eVal" :value="eVal">{{ eVal }}</option>--> |
||||
<template v-slot:selection="{item}"> |
||||
<div class="d-100 pl-4" :class="{ |
||||
<div class="d-100" :class="{ |
||||
'text-center' : !isForm |
||||
}"> |
||||
<v-chip small :color="colors[enumValues.indexOf(item) % colors.length]">{{ item }}</v-chip> |
||||
<v-chip small :color="colors[enumValues.indexOf(item) % colors.length]" class="ma-1">{{ item }}</v-chip> |
||||
</div> |
||||
</template> |
||||
<template v-slot:item="{item}"> |
@ -0,0 +1,54 @@
|
||||
<template> |
||||
<v-pagination |
||||
v-if="count !== Infinity" |
||||
style="max-width: 100%" |
||||
v-model="page" |
||||
:length="Math.ceil(count / size)" |
||||
:total-visible="8" |
||||
@input="$emit('input',page)" |
||||
color="primary lighten-2" |
||||
></v-pagination> |
||||
<div v-else class="mx-auto d-flex align-center mt-n1 " style="max-width:250px"> |
||||
<span class="caption" style="white-space: nowrap"> Change page:</span> |
||||
<v-text-field |
||||
class="ml-1 caption" |
||||
:full-width="false" |
||||
outlined |
||||
dense |
||||
hide-details |
||||
v-model="page" |
||||
@keydown.enter="$emit('input',page)" |
||||
type="number" |
||||
> |
||||
<template #append> |
||||
<x-icon tooltip="Change page" small icon.class="mt-1" @click="$emit('input',page)">mdi-keyboard-return |
||||
</x-icon> |
||||
</template> |
||||
</v-text-field> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
props: { |
||||
count: Number, |
||||
value: Number, |
||||
size: Number, |
||||
}, |
||||
data: () => ({ |
||||
page: 1 |
||||
}), |
||||
mounted() { |
||||
this.page = this.value; |
||||
}, |
||||
watch: { |
||||
value(v) { |
||||
this.page = v; |
||||
} |
||||
}, |
||||
name: "pagination" |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
</style> |
@ -0,0 +1,144 @@
|
||||
<template> |
||||
<div> |
||||
<v-lazy> |
||||
<has-many-cell |
||||
ref="cell" |
||||
v-if="hm" |
||||
:row="row" |
||||
:value="row[`${hm._tn}List`]" |
||||
:meta="meta" |
||||
:hm="hm" |
||||
:nodes="nodes" |
||||
:active="active" |
||||
:sql-ui="sqlUi" |
||||
:is-new="isNew" |
||||
:is-form="isForm" |
||||
:breadcrumbs="breadcrumbs" |
||||
v-on="$listeners" |
||||
/> |
||||
<many-to-many-cell |
||||
ref="cell" |
||||
v-else-if="mm" |
||||
:row="row" |
||||
:value="row[`${mm._rtn}MMList`]" |
||||
:meta="meta" |
||||
:mm="mm" |
||||
:nodes="nodes" |
||||
:sql-ui="sqlUi" |
||||
:active="active" |
||||
:is-new="isNew" |
||||
:api="api" |
||||
:is-form="isForm" |
||||
:breadcrumbs="breadcrumbs" |
||||
v-on="$listeners" |
||||
/> |
||||
<belongs-to-cell |
||||
ref="cell" |
||||
:disabled-columns="disabledColumns" |
||||
v-else-if="bt" |
||||
:active="active" |
||||
:row="row" |
||||
:value="row[`${bt._rtn}Read`]" |
||||
:meta="meta" |
||||
:bt="bt" |
||||
:nodes="nodes" |
||||
:api="api" |
||||
:sql-ui="sqlUi" |
||||
:is-new="isNew" |
||||
:is-form="isForm" |
||||
:breadcrumbs="breadcrumbs" |
||||
v-on="$listeners" |
||||
/> |
||||
</v-lazy> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import hasManyCell from "@/components/project/spreadsheet/components/virtualCell/hasManyCell"; |
||||
import manyToManyCell from "@/components/project/spreadsheet/components/virtualCell/manyToManyCell"; |
||||
import belongsToCell from "@/components/project/spreadsheet/components/virtualCell/belogsToCell"; |
||||
|
||||
// todo: optimize parent/child meta extraction |
||||
|
||||
export default { |
||||
name: "virtual-cell", |
||||
components: { |
||||
belongsToCell, |
||||
manyToManyCell, |
||||
hasManyCell |
||||
}, |
||||
props: { |
||||
breadcrumbs: { |
||||
type: Array, |
||||
default() { |
||||
return []; |
||||
} |
||||
}, |
||||
column: [Object], |
||||
row: [Object], |
||||
nodes: [Object], |
||||
meta: [Object], |
||||
api: [Object, Function], |
||||
active: Boolean, |
||||
sqlUi: [Object, Function], |
||||
isNew: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
isForm: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
disabledColumns: Object |
||||
}, |
||||
computed: { |
||||
hm() { |
||||
return this.column && this.column.hm; |
||||
}, |
||||
bt() { |
||||
return this.column && this.column.bt; |
||||
}, |
||||
mm() { |
||||
return this.column && this.column.mm; |
||||
} |
||||
}, |
||||
methods: { |
||||
async save(row) { |
||||
if (row && this.$refs.cell && this.$refs.cell.saveLocalState) { |
||||
try { |
||||
await this.$refs.cell.saveLocalState(row); |
||||
} catch (e) { |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,399 @@
|
||||
<template> |
||||
<div class="d-flex d-100 chips-wrapper" :class="{active}"> |
||||
<template v-if="!isForm"> |
||||
<div class="chips d-flex align-center img-container flex-grow-1 hm-items"> |
||||
<template v-if="value || localState"> |
||||
<item-chip |
||||
:active="active" |
||||
:item="value" |
||||
:value="cellValue" |
||||
@edit="editParent" |
||||
@unlink="unlink" |
||||
></item-chip> |
||||
</template> |
||||
</div> |
||||
<div class="action align-center justify-center px-1 flex-shrink-1" |
||||
:class="{'d-none': !active, 'd-flex':active }"> |
||||
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{ |
||||
value ? 'mdi-arrow-expand' : 'mdi-plus' |
||||
}} |
||||
</x-icon> |
||||
</div> |
||||
</template> |
||||
<list-items |
||||
:key="parentId" |
||||
v-if="newRecordModal" |
||||
:size="10" |
||||
:meta="parentMeta" |
||||
:primary-col="parentPrimaryCol" |
||||
:primary-key="parentPrimaryKey" |
||||
v-model="newRecordModal" |
||||
:api="parentApi" |
||||
@add-new-record="insertAndMapNewParentRecord" |
||||
@add="addChildToParent" |
||||
:query-params="parentQueryParams" |
||||
/> |
||||
|
||||
<list-child-items |
||||
ref="childList" |
||||
v-if="parentMeta && isForm" |
||||
:isForm="isForm" |
||||
:local-state="localState? [localState] : []" |
||||
:is-new="isNew" |
||||
:size="10" |
||||
:parent-meta="parentMeta" |
||||
:meta="parentMeta" |
||||
:primary-col="parentPrimaryCol" |
||||
:primary-key="parentPrimaryKey" |
||||
:api="parentApi" |
||||
:query-params="{ |
||||
...parentQueryParams, |
||||
where: `(${parentPrimaryKey},eq,${parentId})` |
||||
}" |
||||
@new-record="showNewRecordModal" |
||||
@edit="editParent" |
||||
@unlink="unlink" |
||||
:bt="value" |
||||
/> |
||||
|
||||
<v-dialog |
||||
:overlay-opacity="0.8" |
||||
v-if="selectedParent" |
||||
width="1000px" |
||||
max-width="100%" |
||||
class=" mx-auto" |
||||
v-model="expandFormModal"> |
||||
<component |
||||
v-if="selectedParent" |
||||
:is="form" |
||||
:db-alias="nodes.dbAlias" |
||||
:has-many="parentMeta.hasMany" |
||||
:belongs-to="parentMeta.belongsTo" |
||||
:table="parentMeta.tn" |
||||
:old-row="{...selectedParent}" |
||||
:meta="parentMeta" |
||||
:sql-ui="sqlUi" |
||||
:primary-value-column="parentPrimaryCol" |
||||
:api="parentApi" |
||||
:available-columns="parentAvailableColumns" |
||||
:nodes="nodes" |
||||
:query-params="parentQueryParams" |
||||
:is-new.sync="isNewParent" |
||||
icon-color="warning" |
||||
ref="expandedForm" |
||||
v-model="selectedParent" |
||||
@cancel="selectedParent = null" |
||||
@input="onParentSave" |
||||
:breadcrumbs="breadcrumbs" |
||||
></component> |
||||
|
||||
</v-dialog> |
||||
|
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; |
||||
import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems"; |
||||
import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip"; |
||||
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; |
||||
|
||||
export default { |
||||
name: "belongs-to-cell", |
||||
components: {ListChildItems, ItemChip, ListItems}, |
||||
props: { |
||||
breadcrumbs: { |
||||
type: Array, |
||||
default() { |
||||
return []; |
||||
} |
||||
}, |
||||
isForm: Boolean, |
||||
value: [Object, Array], |
||||
meta: [Object], |
||||
bt: Object, |
||||
nodes: [Object], |
||||
row: [Object], |
||||
api: [Object, Function], |
||||
sqlUi: [Object, Function], |
||||
active: Boolean, |
||||
isNew: Boolean, |
||||
disabledColumns: Object |
||||
}, |
||||
data: () => ({ |
||||
newRecordModal: false, |
||||
parentListModal: false, |
||||
// parentMeta: null, |
||||
list: null, |
||||
childList: null, |
||||
dialogShow: false, |
||||
confirmAction: null, |
||||
confirmMessage: '', |
||||
selectedParent: null, |
||||
isNewParent: false, |
||||
expandFormModal: false, |
||||
localState: null, |
||||
pid: null |
||||
}), |
||||
async mounted() { |
||||
if (this.isForm) { |
||||
await this.loadParentMeta() |
||||
} |
||||
}, |
||||
methods: { |
||||
async onParentSave(parent) { |
||||
if (this.isNewParent) { |
||||
await this.addChildToParent(parent) |
||||
} else { |
||||
this.$emit('loadTableData') |
||||
} |
||||
}, |
||||
async insertAndMapNewParentRecord() { |
||||
await this.loadParentMeta(); |
||||
this.newRecordModal = false; |
||||
this.isNewParent = true; |
||||
this.selectedParent = {}; |
||||
this.expandFormModal = true; |
||||
}, |
||||
|
||||
async unlink() { |
||||
const column = this.meta.columns.find(c => c.cn === this.bt.cn); |
||||
const _cn = column._cn; |
||||
if (this.isNew) { |
||||
this.$emit('updateCol', this.row, _cn, null); |
||||
this.localState = null; |
||||
return |
||||
} |
||||
if (column.rqd) { |
||||
this.$toast.info('Unlink is not possible, instead map to another parent.').goAway(3000) |
||||
return |
||||
} |
||||
const id = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); |
||||
await this.api.update(id, {[_cn]: null}, this.row) |
||||
this.$emit('loadTableData') |
||||
if (this.isForm && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
}, |
||||
async showParentListModal() { |
||||
this.parentListModal = true; |
||||
await this.loadParentMeta(); |
||||
const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); |
||||
const _cn = this.parentMeta.columns.find(c => c.cn === this.hm.cn)._cn; |
||||
this.childList = await this.parentApi.paginatedList({ |
||||
where: `(${_cn},eq,${pid})` |
||||
}) |
||||
}, |
||||
async removeChild(child) { |
||||
this.dialogShow = true; |
||||
this.confirmMessage = |
||||
'Do you want to delete the record?'; |
||||
this.confirmAction = async act => { |
||||
if (act === 'hideDialog') { |
||||
this.dialogShow = false; |
||||
} else { |
||||
const id = this.parentMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
await this.parentApi.delete(id) |
||||
this.pid = null; |
||||
this.dialogShow = false; |
||||
this.$emit('loadTableData') |
||||
if (this.isForm && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
async loadParentMeta() { |
||||
// todo: optimize |
||||
if (!this.parentMeta) { |
||||
await this.$store.dispatch('meta/ActLoadMeta', { |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias, |
||||
tn: this.bt.rtn |
||||
}) |
||||
// const parentTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
// env: this.nodes.env, |
||||
// dbAlias: this.nodes.dbAlias |
||||
// }, 'tableXcModelGet', { |
||||
// tn: this.bt.rtn |
||||
// }]); |
||||
// this.parentMeta = JSON.parse(parentTableData.meta) |
||||
} |
||||
}, |
||||
async showNewRecordModal() { |
||||
await this.loadParentMeta(); |
||||
this.newRecordModal = true; |
||||
}, |
||||
async addChildToParent(parent) { |
||||
|
||||
const pid = this.parentMeta.columns.filter((c) => c.pk).map(c => parent[c._cn]).join('___'); |
||||
const id = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); |
||||
const _cn = this.meta.columns.find(c => c.cn === this.bt.cn)._cn; |
||||
|
||||
if (this.isNew) { |
||||
this.localState = parent; |
||||
this.$emit('updateCol', this.row, _cn, +pid || pid) |
||||
this.newRecordModal = false; |
||||
return |
||||
} |
||||
|
||||
await this.api.update(id, { |
||||
[_cn]: +pid |
||||
}, { |
||||
[_cn]: this.value && this.value[this.parentPrimaryKey] |
||||
}); |
||||
this.pid = pid; |
||||
|
||||
this.newRecordModal = false; |
||||
|
||||
this.$emit('loadTableData') |
||||
if (this.isForm && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
}, |
||||
async editParent(parent) { |
||||
await this.loadParentMeta(); |
||||
this.isNewParent = false; |
||||
this.selectedParent = parent; |
||||
this.expandFormModal = true; |
||||
setTimeout(() => { |
||||
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
||||
}, 500) |
||||
}, |
||||
}, |
||||
computed: { |
||||
parentMeta() { |
||||
return this.$store.state.meta.metas[this.bt.rtn]; |
||||
}, |
||||
parentApi() { |
||||
return this.parentMeta && this.parentMeta._tn ? |
||||
ApiFactory.create(this.$store.getters['project/GtrProjectType'], |
||||
this.parentMeta && this.parentMeta._tn, this.parentMeta && this.parentMeta.columns, this, this.parentMeta) : null; |
||||
}, |
||||
parentId() { |
||||
return this.pid ?? (this.value && this.parentMeta && this.parentMeta.columns.filter((c) => c.pk).map(c => this.value[c._cn]).join('___')) |
||||
}, |
||||
parentPrimaryCol() { |
||||
return this.parentMeta && (this.parentMeta.columns.find(c => c.pv) || {})._cn |
||||
}, |
||||
parentPrimaryKey() { |
||||
return this.parentMeta && (this.parentMeta.columns.find(c => c.pk) || {})._cn |
||||
}, |
||||
parentQueryParams() { |
||||
if (!this.parentMeta) return {} |
||||
return { |
||||
childs: (this.parentMeta && this.parentMeta.v && this.parentMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '', |
||||
parents: (this.parentMeta && this.parentMeta.v && this.parentMeta.v.filter(v => v.bt).map(({bt}) => bt.rtn).join()) || '', |
||||
many: (this.parentMeta && this.parentMeta.v && this.parentMeta.v.filter(v => v.mm).map(({mm}) => mm.rtn).join()) || '' |
||||
} |
||||
}, |
||||
parentAvailableColumns() { |
||||
const hideCols = ['created_at', 'updated_at']; |
||||
if (!this.parentMeta) return []; |
||||
|
||||
const columns = []; |
||||
if (this.parentMeta.columns) { |
||||
columns.push(...this.parentMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn) && !((this.parentMeta.v || []).some(v => v.bt && v.bt.cn === c.cn)))) |
||||
} |
||||
if (this.parentMeta.v) { |
||||
columns.push(...this.parentMeta.v.map(v => ({...v, virtual: 1}))); |
||||
} |
||||
return columns; |
||||
}, |
||||
// todo: |
||||
form() { |
||||
return this.selectedParent ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span'; |
||||
}, |
||||
cellValue() { |
||||
if (this.value || this.localState) { |
||||
if (this.parentMeta && this.parentPrimaryCol) { |
||||
return (this.value || this.localState)[this.parentPrimaryCol] |
||||
} |
||||
return Object.values(this.value || this.localState)[1] |
||||
} |
||||
} |
||||
}, |
||||
watch: { |
||||
isNew(n, o) { |
||||
if (!n && o) { |
||||
this.localState = null |
||||
} |
||||
} |
||||
}, |
||||
created() { |
||||
this.loadParentMeta(); |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.items-container { |
||||
overflow-x: visible; |
||||
max-height: min(500px, 60vh); |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
.primary-value { |
||||
.primary-key { |
||||
display: none; |
||||
margin-left: .5em; |
||||
} |
||||
|
||||
&:hover .primary-key { |
||||
display: inline; |
||||
} |
||||
} |
||||
|
||||
.child-card { |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
box-shadow: 0 0 .2em var(--v-textColor-lighten5) |
||||
} |
||||
} |
||||
|
||||
.hm-items { |
||||
flex-wrap: wrap; |
||||
row-gap: 3px; |
||||
gap: 3px; |
||||
margin: 3px auto; |
||||
} |
||||
|
||||
.chips-wrapper { |
||||
.chips { |
||||
max-width: 100%; |
||||
} |
||||
|
||||
&.active { |
||||
.chips { |
||||
max-width: calc(100% - 22px); |
||||
} |
||||
} |
||||
} |
||||
|
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,43 @@
|
||||
<template> |
||||
<v-chip |
||||
class="chip" |
||||
:class="{active}" |
||||
small |
||||
text-color="textColor" |
||||
:color="isDark ? '' : 'primary lighten-5'" |
||||
@click="active && $emit('edit',item)" |
||||
> |
||||
<span class="name" :title="value">{{ value }}</span> |
||||
<div v-show="active" class="mr-n1 ml-2"> |
||||
<x-icon |
||||
:color="['text' , 'textLight']" |
||||
x-small |
||||
icon.class="unlink-icon" |
||||
@click.stop="$emit('unlink',item)" |
||||
>mdi-close-thick |
||||
</x-icon> |
||||
</div> |
||||
</v-chip> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
props: { |
||||
value: String, |
||||
active: Boolean, |
||||
item: Object |
||||
}, |
||||
name: "item-chip" |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.chip { |
||||
max-width: max(100%, 60px); |
||||
|
||||
.name { |
||||
text-overflow: ellipsis; |
||||
overflow: hidden; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,220 @@
|
||||
<template> |
||||
<!-- <v-dialog v-model="show" width="600">--> |
||||
<v-card width="600" color=""> |
||||
<v-card-title v-if="!isForm" class="textColor--text mx-2" :class="{'py-2':isForm}"> |
||||
<span v-if="!isForm">{{ meta ? meta._tn : 'Children' }}</span> |
||||
<v-spacer> |
||||
</v-spacer> |
||||
<v-icon small class="mr-1" @click="loadData()">mdi-reload</v-icon> |
||||
<v-btn |
||||
small |
||||
class="caption" |
||||
color="primary" |
||||
@click="$emit('new-record')" |
||||
> |
||||
<v-icon |
||||
small>mdi-link |
||||
</v-icon> |
||||
Link to '{{ meta._tn }}' |
||||
</v-btn> |
||||
</v-card-title> |
||||
<v-card-text> |
||||
<div class="items-container pt-2 mb-n4" :class="{'mx-n2' : isForm}"> |
||||
<div class="text-right mb-2 mt-n2 mx-2"> |
||||
<v-btn |
||||
v-if="isForm" |
||||
x-small |
||||
class="caption" |
||||
color="primary" |
||||
outlined |
||||
@click="$emit('new-record')" |
||||
> |
||||
<v-icon |
||||
x-small>mdi-link |
||||
</v-icon> |
||||
Link to '{{ meta._tn }}' |
||||
</v-btn> |
||||
</div> |
||||
<template v-if="isDataAvail"> |
||||
<v-card |
||||
v-for="(ch,i) in ((data && data.list) || localState)" |
||||
class="mx-2 mb-2 child-list-modal child-card" |
||||
outlined |
||||
:key="i" |
||||
@click="$emit('edit',ch)" |
||||
> |
||||
<div class="remove-child-icon d-flex align-center"> |
||||
<x-icon |
||||
:tooltip="`Unlink this '${meta._tn}' from '${parentMeta._tn}'`" |
||||
:color="['error','grey']" |
||||
small |
||||
@click.stop="$emit('unlink',ch,i)" |
||||
icon.class="mr-1 mt-n1" |
||||
>mdi-link-variant-remove |
||||
</x-icon> |
||||
<x-icon |
||||
v-if="!mm && !bt" |
||||
:tooltip="`Delete row in '${meta._tn}'`" |
||||
:color="['error','grey']" |
||||
small |
||||
@click.stop="$emit('delete',ch,i)" |
||||
>mdi-delete-outline |
||||
</x-icon> |
||||
</div> |
||||
|
||||
<v-card-title class="primary-value textColor--text text--lighten-2">{{ ch[primaryCol] }} |
||||
<span class="grey--text caption primary-key ml-1" |
||||
v-if="primaryKey"> (Primary Key : {{ ch[primaryKey] }})</span> |
||||
</v-card-title> |
||||
</v-card> |
||||
</template> |
||||
|
||||
<div v-else-if="data || localState" class="text-center textLight--text" |
||||
:class="{'pt-6 pb-4' : !isForm , 'pt-4 pb-3':isForm}"> |
||||
No item{{ bt ? '' : 's' }} found |
||||
</div> |
||||
|
||||
<div v-if="isForm" class="mb-2 d-flex align-center justify-center"> |
||||
<pagination |
||||
v-if="!bt && data && data.count > 1" |
||||
:size="size" |
||||
:count="data && data.count" |
||||
v-model="page" |
||||
@input="loadData" |
||||
></pagination> |
||||
</div> |
||||
</div> |
||||
</v-card-text> |
||||
<v-card-actions v-if="!isForm" class="justify-center flex-column" :class="{'py-0':isForm}"> |
||||
<pagination |
||||
v-if="!bt && data && data.count > 1" |
||||
:size="size" |
||||
:count="data && data.count" |
||||
v-model="page" |
||||
@input="loadData" |
||||
class="mb-3" |
||||
></pagination> |
||||
</v-card-actions> |
||||
</v-card> |
||||
<!-- </v-dialog>--> |
||||
|
||||
</template> |
||||
|
||||
<script> |
||||
import Pagination from "@/components/project/spreadsheet/components/pagination"; |
||||
|
||||
export default { |
||||
name: "listChildItems", |
||||
components: {Pagination}, |
||||
props: { |
||||
isForm: Boolean, |
||||
bt: Object, |
||||
localState: [Array], |
||||
isNew: Boolean, |
||||
value: Boolean, |
||||
title: { |
||||
type: String, |
||||
default: 'Link Record' |
||||
}, |
||||
queryParams: { |
||||
type: Object, |
||||
default() { |
||||
return {}; |
||||
} |
||||
}, |
||||
primaryKey: String, |
||||
primaryCol: String, |
||||
meta: Object, |
||||
parentMeta: Object, |
||||
size: Number, |
||||
api: [Object, Function], |
||||
mm: [Object, Boolean] |
||||
}, |
||||
data: () => ({ |
||||
data: null, |
||||
page: 1 |
||||
}), |
||||
mounted() { |
||||
this.loadData(); |
||||
}, |
||||
methods: { |
||||
async loadData() { |
||||
if (!this.api || this.isNew) return; |
||||
this.data = await this.api.paginatedList({ |
||||
limit: this.size, |
||||
offset: this.size * (this.page - 1), |
||||
...this.queryParams |
||||
}) |
||||
} |
||||
}, |
||||
computed: { |
||||
isDataAvail() { |
||||
return (this.data && this.data.list && this.data.list.length) || (this.localState && this.localState.length); |
||||
}, |
||||
show: { |
||||
set(v) { |
||||
this.$emit('input', v) |
||||
}, get() { |
||||
return this.value; |
||||
} |
||||
} |
||||
}, |
||||
watch: { |
||||
queryParams() { |
||||
this.loadData(); |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
.child-list-modal { |
||||
position: relative; |
||||
|
||||
.remove-child-icon { |
||||
position: absolute; |
||||
right: 10px; |
||||
top: 10px; |
||||
bottom: 10px; |
||||
opacity: 0; |
||||
} |
||||
|
||||
&:hover .remove-child-icon { |
||||
opacity: 1; |
||||
} |
||||
|
||||
} |
||||
|
||||
.items-container { |
||||
overflow-x: visible; |
||||
max-height: min(500px, 60vh); |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
</style> |
||||
|
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,118 @@
|
||||
<template> |
||||
<v-dialog v-model="show" width="600" content-class="dialog"> |
||||
<v-icon small class="close-icon" @click="$emit('input',false)">mdi-close</v-icon> |
||||
<list-child-items |
||||
v-if="show" |
||||
ref="child" |
||||
:local-state="localState" |
||||
:is-new="isNew" |
||||
:size="10" |
||||
:meta="meta" |
||||
:parent-meta="meta" |
||||
:primary-col="primaryCol" |
||||
:primary-key="primaryKey" |
||||
:api="api" |
||||
:query-params="queryParams" |
||||
v-bind="$attrs" |
||||
v-on="$listeners" |
||||
|
||||
/> |
||||
</v-dialog> |
||||
|
||||
</template> |
||||
|
||||
<script> |
||||
import Pagination from "@/components/project/spreadsheet/components/pagination"; |
||||
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; |
||||
|
||||
export default { |
||||
name: "listChildItemsModal", |
||||
components: {ListChildItems, Pagination}, |
||||
props: { |
||||
localState: Array, |
||||
isNew: Boolean, |
||||
value: Boolean, |
||||
title: { |
||||
type: String, |
||||
default: 'Link Record' |
||||
}, |
||||
queryParams: { |
||||
type: Object, |
||||
default() { |
||||
return {}; |
||||
} |
||||
}, |
||||
primaryKey: String, |
||||
primaryCol: String, |
||||
meta: Object, |
||||
parentMeta: Object, |
||||
size: Number, |
||||
api: [Object, Function], |
||||
mm: [Object, Boolean] |
||||
}, |
||||
data: () => ({ |
||||
data: null, |
||||
page: 1 |
||||
}), |
||||
mounted() { |
||||
}, |
||||
methods: { |
||||
async loadData() { |
||||
if (this.$refs && this.$refs.child) { |
||||
this.$refs.child.loadData(); |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
show: { |
||||
set(v) { |
||||
this.$emit('input', v) |
||||
}, get() { |
||||
return this.value; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
::v-deep { |
||||
.dialog { |
||||
position: relative; |
||||
|
||||
.close-icon { |
||||
width: auto; |
||||
position: absolute; |
||||
right: 10px; |
||||
top: 10px; |
||||
z-index: 9; |
||||
} |
||||
} |
||||
} |
||||
|
||||
</style> |
||||
|
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,225 @@
|
||||
<template> |
||||
<v-dialog v-model="show" width="600" content-class="dialog"> |
||||
<v-icon small class="close-icon" @click="$emit('input',false)">mdi-close</v-icon> |
||||
<v-card width="600" > |
||||
<v-card-title class="textColor--text mx-2 justify-center">{{ title }} |
||||
|
||||
</v-card-title> |
||||
|
||||
<v-card-title> |
||||
<v-text-field |
||||
hide-details |
||||
dense |
||||
outlined |
||||
placeholder="Search records" |
||||
class=" caption search-field ml-2" |
||||
/> |
||||
<v-spacer></v-spacer> |
||||
|
||||
<v-icon small class="mr-1" @click="loadData()">mdi-reload</v-icon> |
||||
<v-btn small class="caption mr-2" color="primary" @click="$emit('add-new-record')"> |
||||
<v-icon small>mdi-plus</v-icon> |
||||
New Record |
||||
</v-btn> |
||||
|
||||
</v-card-title> |
||||
|
||||
<v-card-text> |
||||
<div class="items-container"> |
||||
<template v-if="data && data.list && data.list.length"> |
||||
<v-card |
||||
v-for="(ch,i) in data.list" |
||||
class="ma-2 child-card" |
||||
outlined |
||||
v-ripple |
||||
@click="$emit('add',ch)" |
||||
:key="i" |
||||
> |
||||
<v-card-text class="primary-value textColor--text text--lighten-2 d-flex"> |
||||
<span class="font-weight-bold"> {{ ch[primaryCol] }} </span> |
||||
<span class="grey--text caption primary-key " |
||||
v-if="primaryKey">(Primary Key : {{ ch[primaryKey] }})</span> |
||||
<v-spacer/> |
||||
<v-chip v-if="hm && ch[`${hm._rtn}Read`] && ch[`${hm._rtn}Read`][hmParentPrimaryValCol]" x-small> |
||||
{{ ch[`${hm._rtn}Read`][hmParentPrimaryValCol] }} |
||||
</v-chip> |
||||
</v-card-text> |
||||
</v-card> |
||||
|
||||
|
||||
</template> |
||||
|
||||
<div v-else-if="data" class="text-center py-15 textLight--text"> |
||||
No items found |
||||
</div> |
||||
</div> |
||||
</v-card-text> |
||||
<v-card-actions class="justify-center py-2 flex-column"> |
||||
<pagination |
||||
v-if="data && data.list && data.list.length" |
||||
:size="size" |
||||
:count="data.count" |
||||
v-model="page" |
||||
@input="loadData" |
||||
class="mb-3" |
||||
></pagination> |
||||
</v-card-actions> |
||||
</v-card> |
||||
</v-dialog> |
||||
|
||||
</template> |
||||
|
||||
<script> |
||||
import Pagination from "@/components/project/spreadsheet/components/pagination"; |
||||
|
||||
export default { |
||||
name: "listItems", |
||||
components: {Pagination}, |
||||
props: { |
||||
value: Boolean, |
||||
hm: [Object, Function], |
||||
title: { |
||||
type: String, |
||||
default: 'Link Record' |
||||
}, |
||||
queryParams: { |
||||
type: Object, |
||||
default() { |
||||
return {}; |
||||
} |
||||
}, |
||||
primaryKey: String, |
||||
primaryCol: String, |
||||
meta: Object, |
||||
size: Number, |
||||
api: [Object, Function], |
||||
mm: [Object, Function], |
||||
parentId: [String, Number], |
||||
parentMeta:[Object] |
||||
}, |
||||
data: () => ({ |
||||
data: null, |
||||
page: 1 |
||||
}), |
||||
mounted() { |
||||
this.loadData(); |
||||
}, |
||||
methods: { |
||||
async loadData() { |
||||
if (!this.api) return; |
||||
|
||||
if (this.mm) { |
||||
this.data = await this.api.paginatedM2mNotChildrenList({ |
||||
limit: this.size, |
||||
offset: this.size * (this.page - 1), |
||||
...this.queryParams |
||||
}, this.mm.vtn,this.parentId) |
||||
|
||||
} else { |
||||
this.data = await this.api.paginatedList({ |
||||
limit: this.size, |
||||
offset: this.size * (this.page - 1), |
||||
...this.queryParams |
||||
}) |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
show: { |
||||
set(v) { |
||||
this.$emit('input', v) |
||||
}, get() { |
||||
return this.value; |
||||
} |
||||
}, |
||||
hmParentPrimaryValCol(){ |
||||
return this.hm && |
||||
this.parentMeta && |
||||
this.parentMeta.columns.find(v => v.pv)._cn |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.child-list-modal { |
||||
position: relative; |
||||
|
||||
.remove-child-icon { |
||||
position: absolute; |
||||
right: 10px; |
||||
top: 10px; |
||||
bottom: 10px; |
||||
opacity: 0; |
||||
} |
||||
|
||||
&:hover .remove-child-icon { |
||||
opacity: 1; |
||||
} |
||||
|
||||
} |
||||
|
||||
.child-card { |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
box-shadow: 0 0 .2em var(--v-textColor-lighten5) |
||||
} |
||||
} |
||||
|
||||
|
||||
.primary-value { |
||||
.primary-key { |
||||
display: none; |
||||
margin-left: .5em; |
||||
} |
||||
|
||||
&:hover .primary-key { |
||||
display: inline; |
||||
} |
||||
} |
||||
|
||||
.items-container { |
||||
overflow-x: visible; |
||||
max-height: min(500px, 60vh); |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
::v-deep { |
||||
.dialog { |
||||
position: relative; |
||||
|
||||
.close-icon { |
||||
width: auto; |
||||
position: absolute; |
||||
right: 10px; |
||||
top: 10px; |
||||
z-index: 9; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,467 @@
|
||||
<template> |
||||
<div class="d-flex d-100 chips-wrapper" :class="{active}"> |
||||
<template v-if="!isForm"> |
||||
<div class="chips d-flex align-center img-container flex-grow-1 hm-items"> |
||||
<template v-if="value||localState"> |
||||
<item-chip |
||||
v-for="(ch,i) in (value|| localState)" |
||||
:active="active" |
||||
:item="ch" |
||||
:value="getCellValue(ch)" |
||||
:key="i" |
||||
@edit="editChild" |
||||
@unlink="unlinkChild" |
||||
></item-chip> |
||||
|
||||
<span v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" |
||||
@click="showChildListModal">more... |
||||
</span> |
||||
</template> |
||||
</div> |
||||
<div class="actions align-center justify-center px-1 flex-shrink-1" |
||||
:class="{'d-none': !active, 'd-flex':active }"> |
||||
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon> |
||||
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon> |
||||
</div> |
||||
</template> |
||||
|
||||
<list-items |
||||
v-if="newRecordModal" |
||||
:hm="hm" |
||||
:size="10" |
||||
:meta="childMeta" |
||||
:primary-col="childPrimaryCol" |
||||
:primary-key="childPrimaryKey" |
||||
v-model="newRecordModal" |
||||
:api="childApi" |
||||
:parent-meta="meta" |
||||
@add-new-record="insertAndAddNewChildRecord" |
||||
@add="addChildToParent" |
||||
:query-params="{ |
||||
...childQueryParams, |
||||
where: isNew ? null :`~not(${childForeignKey},eq,${parentId})~or(${childForeignKey},is,null)`, |
||||
}"/> |
||||
|
||||
<list-child-items |
||||
:is="isForm ? 'list-child-items' : 'list-child-items-modal'" |
||||
:isForm="isForm" |
||||
ref="childList" |
||||
v-if="childMeta && (childListModal || isForm)" |
||||
v-model="childListModal" |
||||
:local-state.sync="localState" |
||||
:is-new="isNew" |
||||
:size="10" |
||||
:meta="childMeta" |
||||
:parent-meta="meta" |
||||
:primary-col="childPrimaryCol" |
||||
:primary-key="childPrimaryKey" |
||||
:api="childApi" |
||||
:query-params="{ |
||||
...childQueryParams, |
||||
where: `(${childForeignKey},eq,${parentId})` |
||||
}" |
||||
@new-record="showNewRecordModal" |
||||
@edit="editChild" |
||||
@unlink="unlinkChild" |
||||
@delete="deleteChild" |
||||
/> |
||||
|
||||
<dlg-label-submit-cancel |
||||
type="primary" |
||||
v-if="dialogShow" |
||||
:actionsMtd="confirmAction" |
||||
:dialogShow="dialogShow" |
||||
:heading="confirmMessage" |
||||
> |
||||
</dlg-label-submit-cancel> |
||||
|
||||
<v-dialog |
||||
:overlay-opacity="0.8" |
||||
v-if="selectedChild" |
||||
width="1000px" |
||||
max-width="100%" |
||||
class=" mx-auto" |
||||
v-model="expandFormModal"> |
||||
<component |
||||
v-if="selectedChild" |
||||
:is="form" |
||||
:db-alias="nodes.dbAlias" |
||||
:has-many="childMeta.hasMany" |
||||
:belongs-to="childMeta.belongsTo" |
||||
@cancel="selectedChild = null" |
||||
@input="$emit('loadTableData')" |
||||
:table="childMeta.tn" |
||||
v-model="selectedChild" |
||||
:old-row="{...selectedChild}" |
||||
:meta="childMeta" |
||||
:sql-ui="sqlUi" |
||||
:primary-value-column="childPrimaryCol" |
||||
:api="childApi" |
||||
:available-columns="childAvailableColumns" |
||||
icon-color="warning" |
||||
:nodes="nodes" |
||||
:query-params="childQueryParams" |
||||
ref="expandedForm" |
||||
:is-new.sync="isNewChild" |
||||
:disabled-columns="disabledChildColumns" |
||||
:breadcrumbs="breadcrumbs" |
||||
></component> |
||||
|
||||
</v-dialog> |
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; |
||||
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel"; |
||||
import Pagination from "@/components/project/spreadsheet/components/pagination"; |
||||
import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems"; |
||||
import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip"; |
||||
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; |
||||
import listChildItemsModal |
||||
from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal"; |
||||
import {parseIfInteger} from "@/helpers"; |
||||
|
||||
export default { |
||||
name: "has-many-cell", |
||||
components: { |
||||
ListChildItems, |
||||
ItemChip, |
||||
ListItems, |
||||
Pagination, |
||||
DlgLabelSubmitCancel, |
||||
listChildItemsModal |
||||
}, |
||||
props: { |
||||
breadcrumbs: { |
||||
type: Array, |
||||
default() { |
||||
return []; |
||||
} |
||||
}, |
||||
value: [Object, Array], |
||||
meta: [Object], |
||||
hm: Object, |
||||
nodes: [Object], |
||||
row: [Object], |
||||
sqlUi: [Object, Function], |
||||
active: Boolean, |
||||
isNew: Boolean, |
||||
isForm: Boolean, |
||||
}, |
||||
data: () => ({ |
||||
newRecordModal: false, |
||||
childListModal: false, |
||||
// childMeta: null, |
||||
dialogShow: false, |
||||
confirmAction: null, |
||||
confirmMessage: '', |
||||
selectedChild: null, |
||||
expandFormModal: false, |
||||
isNewChild: false, |
||||
localState: [] |
||||
}), |
||||
async mounted() { |
||||
await this.loadChildMeta() |
||||
}, |
||||
methods: { |
||||
async showChildListModal() { |
||||
await this.loadChildMeta(); |
||||
this.childListModal = true; |
||||
}, |
||||
async deleteChild(child) { |
||||
this.dialogShow = true; |
||||
this.confirmMessage = |
||||
'Do you want to delete the record?'; |
||||
this.confirmAction = async act => { |
||||
if (act === 'hideDialog') { |
||||
this.dialogShow = false; |
||||
} else { |
||||
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
try { |
||||
await this.childApi.delete(id) |
||||
this.dialogShow = false; |
||||
this.$emit('loadTableData') |
||||
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
} catch (e) { |
||||
this.$toast.error(e.message) |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
async unlinkChild(child) { |
||||
if (this.isNew) { |
||||
this.localState.splice(this.localState.indexOf(child), 1) |
||||
return; |
||||
} |
||||
|
||||
await this.loadChildMeta(); |
||||
const column = this.childMeta.columns.find(c => c.cn === this.hm.cn); |
||||
if (column.rqd) { |
||||
this.$toast.info('Unlink is not possible, instead add to another record.').goAway(3000) |
||||
return |
||||
} |
||||
const _cn = column._cn; |
||||
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
await this.childApi.update(id, {[_cn]: null}, child) |
||||
this.$emit('loadTableData') |
||||
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
// } |
||||
// } |
||||
}, |
||||
async loadChildMeta() { |
||||
// todo: optimize |
||||
if (!this.childMeta) { |
||||
|
||||
await this.$store.dispatch('meta/ActLoadMeta', { |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias, |
||||
tn: this.hm.tn |
||||
}) |
||||
// const childTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
// env: this.nodes.env, |
||||
// dbAlias: this.nodes.dbAlias |
||||
// }, 'tableXcModelGet', { |
||||
// tn: this.hm.tn |
||||
// }]); |
||||
// this.childMeta = JSON.parse(childTableData.meta); |
||||
// this.childQueryParams = JSON.parse(childTableData.query_params); |
||||
} |
||||
}, |
||||
async showNewRecordModal() { |
||||
await this.loadChildMeta(); |
||||
this.newRecordModal = true; |
||||
}, |
||||
async addChildToParent(child) { |
||||
if (this.isNew) { |
||||
this.localState.push(child); |
||||
this.newRecordModal = false; |
||||
return; |
||||
} |
||||
|
||||
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
const _cn = this.childForeignKey; |
||||
this.newRecordModal = false; |
||||
|
||||
await this.childApi.update(id, { |
||||
[_cn]: parseIfInteger(this.parentId) |
||||
}, { |
||||
[_cn]: child[this.childForeignKey] |
||||
}); |
||||
|
||||
this.$emit('loadTableData') |
||||
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
}, |
||||
async editChild(child) { |
||||
await this.loadChildMeta(); |
||||
this.isNewChild = false; |
||||
this.selectedChild = child; |
||||
this.expandFormModal = true; |
||||
setTimeout(() => { |
||||
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
||||
}, 500) |
||||
}, |
||||
async insertAndAddNewChildRecord() { |
||||
this.newRecordModal = false; |
||||
await this.loadChildMeta(); |
||||
this.isNewChild = true; |
||||
this.selectedChild = { |
||||
[this.childForeignKey]: parseIfInteger(this.parentId) |
||||
}; |
||||
this.expandFormModal = true; |
||||
setTimeout(() => { |
||||
this.$refs.expandedForm && this.$refs.expandedForm.$set(this.$refs.expandedForm.changedColumns, this.childForeignKey, true) |
||||
}, 500) |
||||
}, |
||||
getCellValue(cellObj) { |
||||
if (cellObj) { |
||||
if (this.parentMeta && this.childPrimaryCol) { |
||||
return cellObj[this.childPrimaryCol] |
||||
} |
||||
return Object.values(cellObj)[1] |
||||
} |
||||
}, |
||||
async saveLocalState(row) { |
||||
let child; |
||||
while (child = this.localState.pop()) { |
||||
if (row) { |
||||
// todo: use common method |
||||
const pid = this.meta.columns.filter((c) => c.pk).map(c => row[c._cn]).join('___') |
||||
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
const _cn = this.childForeignKey; |
||||
await this.childApi.update(id, { |
||||
[_cn]: parseIfInteger(pid) |
||||
}, { |
||||
[_cn]: child[this.childForeignKey] |
||||
}); |
||||
} else { |
||||
await this.addChildToParent(child) |
||||
} |
||||
} |
||||
this.$emit('newRecordsSaved'); |
||||
} |
||||
}, |
||||
computed: { |
||||
childMeta() { |
||||
return this.$store.state.meta.metas[this.hm.tn] |
||||
}, |
||||
childApi() { |
||||
return this.childMeta && this.childMeta._tn ? |
||||
ApiFactory.create(this.$store.getters['project/GtrProjectType'], |
||||
this.childMeta && this.childMeta._tn, this.childMeta && this.childMeta.columns, this, this.childMeta) : null; |
||||
}, |
||||
childPrimaryCol() { |
||||
return this.childMeta && (this.childMeta.columns.find(c => c.pv) || {})._cn |
||||
}, |
||||
primaryCol() { |
||||
return this.meta && (this.meta.columns.find(c => c.pv) || {})._cn |
||||
}, |
||||
childPrimaryKey() { |
||||
return this.childMeta && (this.childMeta.columns.find(c => c.pk) || {})._cn |
||||
}, |
||||
childForeignKey() { |
||||
return this.childMeta && (this.childMeta.columns.find(c => c.cn === this.hm.cn) || {})._cn |
||||
}, |
||||
disabledChildColumns() { |
||||
return {[this.childForeignKey]: true} |
||||
}, |
||||
// todo: |
||||
form() { |
||||
return this.selectedChild ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span'; |
||||
}, |
||||
childAvailableColumns() { |
||||
const hideCols = ['created_at', 'updated_at']; |
||||
if (!this.childMeta) return []; |
||||
|
||||
const columns = []; |
||||
if (this.childMeta.columns) { |
||||
columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn) && !((this.childMeta.v || []).some(v => v.bt && v.bt.cn === c.cn)))) |
||||
} |
||||
if (this.childMeta.v) { |
||||
columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1}))); |
||||
} |
||||
return columns; |
||||
}, |
||||
childQueryParams() { |
||||
if (!this.childMeta) return {} |
||||
return { |
||||
childs: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '', |
||||
parents: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.bt).map(({bt}) => bt.rtn).join()) || '', |
||||
many: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.mm).map(({mm}) => mm.rtn).join()) || '' |
||||
} |
||||
}, |
||||
parentId() { |
||||
return this.meta && this.meta.columns ? this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___') : ''; |
||||
} |
||||
}, |
||||
watch: { |
||||
isNew(n, o) { |
||||
if (!n && o) { |
||||
this.saveLocalState(); |
||||
} |
||||
} |
||||
}, |
||||
created() { |
||||
this.loadChildMeta(); |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.items-container { |
||||
overflow-x: visible; |
||||
max-height: min(500px, 60vh); |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
.primary-value { |
||||
.primary-key { |
||||
display: none; |
||||
margin-left: .5em; |
||||
} |
||||
|
||||
&:hover .primary-key { |
||||
display: inline; |
||||
} |
||||
} |
||||
|
||||
|
||||
.child-card { |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
box-shadow: 0 0 .2em var(--v-textColor-lighten5) |
||||
} |
||||
} |
||||
|
||||
|
||||
.hm-items { |
||||
//min-width: 200px; |
||||
//max-width: 400px; |
||||
flex-wrap: wrap; |
||||
row-gap: 3px; |
||||
gap: 3px; |
||||
margin: 3px auto; |
||||
} |
||||
|
||||
::v-deep { |
||||
.unlink-icon { |
||||
padding: 0px 1px 2px 1px; |
||||
margin-top: 2px; |
||||
margin-right: -2px; |
||||
} |
||||
|
||||
.search-field { |
||||
input { |
||||
max-height: 28px !important; |
||||
} |
||||
|
||||
.v-input__slot { |
||||
min-height: auto !important; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.chips-wrapper { |
||||
.chips { |
||||
max-width: 100%; |
||||
} |
||||
|
||||
&.active { |
||||
.chips { |
||||
max-width: calc(100% - 44px); |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,505 @@
|
||||
<template> |
||||
<div class="d-flex d-100 chips-wrapper" :class="{active}"> |
||||
<template v-if="!isForm"> |
||||
<div class="chips d-flex align-center img-container flex-grow-1 hm-items"> |
||||
<template v-if="(value || localState)"> |
||||
<item-chip v-for="(v,j) in (value || localState)" |
||||
:active="active" |
||||
:item="v" |
||||
:value="getCellValue(v)" |
||||
:key="j" |
||||
@edit="editChild" |
||||
@unlink="unlinkChild" |
||||
></item-chip> |
||||
|
||||
</template> |
||||
<span v-if="value && value.length === 10" class="caption pointer ml-1 grey--text" @click="showChildListModal">more...</span> |
||||
</div> |
||||
<div class="actions align-center justify-center px-1 flex-shrink-1" |
||||
:class="{'d-none': !active, 'd-flex':active }"> |
||||
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon> |
||||
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon> |
||||
</div> |
||||
</template> |
||||
|
||||
|
||||
<list-items |
||||
v-if="newRecordModal" |
||||
:hm="true" |
||||
:size="10" |
||||
:meta="childMeta" |
||||
:primary-col="childPrimaryCol" |
||||
:primary-key="childPrimaryKey" |
||||
v-model="newRecordModal" |
||||
:api="api" |
||||
:mm="mm" |
||||
:parent-id="row && row[parentPrimaryKey]" |
||||
@add-new-record="insertAndAddNewChildRecord" |
||||
@add="addChildToParent" |
||||
:query-params="childQueryParams"/> |
||||
|
||||
<list-child-items |
||||
:is="isForm ? 'list-child-items' : 'list-child-items-modal'" |
||||
:isForm="isForm" |
||||
ref="childList" |
||||
v-if="childMeta && assocMeta && (isForm || childListModal)" |
||||
v-model="childListModal" |
||||
:is-new="isNew" |
||||
:size="10" |
||||
:meta="childMeta" |
||||
:parent-meta="meta" |
||||
:primary-col="childPrimaryCol" |
||||
:primary-key="childPrimaryKey" |
||||
:api="childApi" |
||||
:mm="mm" |
||||
:parent-id="row && row[parentPrimaryKey]" |
||||
:query-params="{...childQueryParams, conditionGraph }" |
||||
:local-state="localState" |
||||
@new-record="showNewRecordModal" |
||||
@edit="editChild" |
||||
@unlink="unlinkChild" |
||||
/> |
||||
<dlg-label-submit-cancel |
||||
type="primary" |
||||
v-if="dialogShow" |
||||
:actionsMtd="confirmAction" |
||||
:dialogShow="dialogShow" |
||||
:heading="confirmMessage" |
||||
> |
||||
</dlg-label-submit-cancel> |
||||
|
||||
<!-- todo : move to listitem component --> |
||||
<v-dialog |
||||
:overlay-opacity="0.8" |
||||
v-if="selectedChild" |
||||
width="1000px" |
||||
max-width="100%" |
||||
class=" mx-auto" |
||||
v-model="expandFormModal"> |
||||
<component |
||||
v-if="selectedChild" |
||||
:is="form" |
||||
:db-alias="nodes.dbAlias" |
||||
:has-many="childMeta.hasMany" |
||||
:belongs-to="childMeta.belongsTo" |
||||
:table="childMeta.tn" |
||||
:old-row="{...selectedChild}" |
||||
:meta="childMeta" |
||||
:sql-ui="sqlUi" |
||||
:primary-value-column="childPrimaryCol" |
||||
:api="childApi" |
||||
:available-columns="childAvailableColumns" |
||||
icon-color="warning" |
||||
:nodes="nodes" |
||||
:query-params="childQueryParams" |
||||
ref="expandedForm" |
||||
:is-new.sync="isNewChild" |
||||
v-model="selectedChild" |
||||
:breadcrumbs="breadcrumbs" |
||||
@cancel="selectedChild = null" |
||||
@input="onChildSave" |
||||
></component> |
||||
|
||||
</v-dialog> |
||||
|
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; |
||||
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel"; |
||||
import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems"; |
||||
import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip"; |
||||
import ListChildItems from "@/components/project/spreadsheet/components/virtualCell/components/listChildItems"; |
||||
import listChildItemsModal |
||||
from "@/components/project/spreadsheet/components/virtualCell/components/listChildItemsModal"; |
||||
import {parseIfInteger} from "@/helpers"; |
||||
|
||||
export default { |
||||
name: "many-to-many-cell", |
||||
components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel, listChildItemsModal}, |
||||
props: { |
||||
breadcrumbs: { |
||||
type: Array, |
||||
default() { |
||||
return []; |
||||
} |
||||
}, |
||||
value: [Object, Array], |
||||
meta: [Object], |
||||
mm: Object, |
||||
nodes: [Object], |
||||
row: [Object], |
||||
api: [Object, Function], |
||||
sqlUi: [Object, Function], |
||||
active: Boolean, |
||||
isNew: Boolean, |
||||
isForm: Boolean, |
||||
}, |
||||
data: () => ({ |
||||
isNewChild: false, |
||||
newRecordModal: false, |
||||
childListModal: false, |
||||
// childMeta: null, |
||||
// assocMeta: null, |
||||
childList: null, |
||||
dialogShow: false, |
||||
confirmAction: null, |
||||
confirmMessage: '', |
||||
selectedChild: null, |
||||
expandFormModal: false, |
||||
localState: [] |
||||
}), |
||||
async mounted() { |
||||
if (this.isForm) { |
||||
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); |
||||
} |
||||
}, |
||||
methods: { |
||||
async onChildSave(child) { |
||||
if (this.isNewChild) { |
||||
this.isNewChild = false; |
||||
await this.addChildToParent(child); |
||||
} else { |
||||
this.$emit('loadTableData') |
||||
} |
||||
}, |
||||
async showChildListModal() { |
||||
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); |
||||
this.childListModal = true; |
||||
}, async unlinkChild(child) { |
||||
if (this.isNew) { |
||||
this.localState.splice(this.localState.indexOf(child), 1) |
||||
return; |
||||
} |
||||
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); |
||||
|
||||
const _pcn = this.meta.columns.find(c => c.cn === this.mm.cn)._cn; |
||||
const _ccn = this.childMeta.columns.find(c => c.cn === this.mm.rcn)._cn; |
||||
|
||||
const apcn = this.assocMeta.columns.find(c => c.cn === this.mm.vcn).cn; |
||||
const accn = this.assocMeta.columns.find(c => c.cn === this.mm.vrcn).cn; |
||||
|
||||
const id = this.assocMeta.columns.filter((c) => c.cn === apcn || c.cn === accn).map(c => c.cn === apcn ? this.row[_pcn] : child[_ccn]).join('___'); |
||||
await this.assocApi.delete(id) |
||||
this.$emit('loadTableData') |
||||
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
}, |
||||
async removeChild(child) { |
||||
this.dialogShow = true; |
||||
this.confirmMessage = |
||||
'Do you want to delete the record?'; |
||||
this.confirmAction = async act => { |
||||
if (act === 'hideDialog') { |
||||
this.dialogShow = false; |
||||
} else { |
||||
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
await this.childApi.delete(id) |
||||
this.dialogShow = false; |
||||
this.$emit('loadTableData') |
||||
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
async loadChildMeta() { |
||||
// todo: optimize |
||||
if (!this.childMeta) { |
||||
await this.$store.dispatch('meta/ActLoadMeta', { |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias, |
||||
tn: this.mm.rtn |
||||
}) |
||||
// const parentTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
// env: this.nodes.env, |
||||
// dbAlias: this.nodes.dbAlias |
||||
// }, 'tableXcModelGet', { |
||||
// tn: this.mm.rtn |
||||
// }]); |
||||
// this.childMeta = JSON.parse(parentTableData.meta) |
||||
} |
||||
}, |
||||
async loadAssociateTableMeta() { |
||||
// todo: optimize |
||||
if (!this.assocMeta) { |
||||
await this.$store.dispatch('meta/ActLoadMeta', { |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias, |
||||
tn: this.mm.vtn |
||||
}) |
||||
// const assocTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
// env: this.nodes.env, |
||||
// dbAlias: this.nodes.dbAlias |
||||
// }, 'tableXcModelGet', { |
||||
// tn: this.mm.vtn |
||||
// }]); |
||||
// this.assocMeta = JSON.parse(assocTableData.meta) |
||||
} |
||||
}, |
||||
async showNewRecordModal() { |
||||
await Promise.all([this.loadChildMeta(), this.loadAssociateTableMeta()]); |
||||
this.newRecordModal = true; |
||||
// this.list = await this.c hildApi.paginatedList({}) |
||||
}, |
||||
async addChildToParent(child) { |
||||
if (this.isNew) { |
||||
this.localState.push(child) |
||||
this.newRecordModal = false; |
||||
return |
||||
} |
||||
const cid = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); |
||||
|
||||
const vcidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vrcn)._cn; |
||||
const vpidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vcn)._cn; |
||||
try { |
||||
await this.assocApi.insert({ |
||||
[vcidCol]: parseIfInteger(cid), |
||||
[vpidCol]: parseIfInteger(pid) |
||||
}); |
||||
|
||||
this.$emit('loadTableData') |
||||
} catch (e) { |
||||
// todo: handle |
||||
console.log(e) |
||||
} |
||||
this.newRecordModal = false; |
||||
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
||||
this.$refs.childList.loadData(); |
||||
} |
||||
|
||||
}, |
||||
|
||||
|
||||
async insertAndAddNewChildRecord() { |
||||
this.newRecordModal = false; |
||||
await this.loadChildMeta(); |
||||
this.isNewChild = true; |
||||
this.selectedChild = { |
||||
[this.childForeignKey]: this.parentId |
||||
}; |
||||
this.expandFormModal = true; |
||||
setTimeout(() => { |
||||
this.$refs.expandedForm && this.$refs.expandedForm.$set(this.$refs.expandedForm.changedColumns, this.childForeignKey, true) |
||||
}, 500) |
||||
}, async editChild(child) { |
||||
await this.loadChildMeta(); |
||||
this.isNewChild = false; |
||||
this.selectedChild = child; |
||||
this.expandFormModal = true; |
||||
setTimeout(() => { |
||||
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
||||
}, 500) |
||||
}, |
||||
async saveLocalState(row) { |
||||
let child; |
||||
while (child = this.localState.pop()) { |
||||
if (row) { |
||||
// todo: use common method |
||||
const cid = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); |
||||
const pid = this.meta.columns.filter((c) => c.pk).map(c => row[c._cn]).join('___'); |
||||
|
||||
const vcidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vrcn)._cn; |
||||
const vpidCol = this.assocMeta.columns.find(c => c.cn === this.mm.vcn)._cn; |
||||
await this.assocApi.insert({ |
||||
[vcidCol]: parseIfInteger(cid), |
||||
[vpidCol]: parseIfInteger(pid) |
||||
}); |
||||
} else { |
||||
await this.addChildToParent(child) |
||||
} |
||||
} |
||||
this.$emit('newRecordsSaved'); |
||||
} |
||||
}, |
||||
computed: { |
||||
getCellValue() { |
||||
return cellObj => { |
||||
if (cellObj) { |
||||
if (this.childPrimaryCol) { |
||||
return cellObj[this.childPrimaryCol] |
||||
} |
||||
return Object.values(cellObj)[1] |
||||
} |
||||
} |
||||
}, |
||||
childMeta() { |
||||
return this.$store.state.meta.metas[this.mm.rtn] |
||||
}, |
||||
assocMeta() { |
||||
return this.$store.state.meta.metas[this.mm.vtn] |
||||
}, |
||||
childApi() { |
||||
return this.childMeta && this.childMeta._tn ? |
||||
ApiFactory.create( |
||||
this.$store.getters['project/GtrProjectType'], |
||||
this.childMeta._tn, |
||||
this.childMeta.columns, |
||||
this, |
||||
this.childMeta |
||||
) : null; |
||||
}, |
||||
assocApi() { |
||||
return this.assocMeta && this.assocMeta._tn ? |
||||
ApiFactory.create( |
||||
this.$store.getters['project/GtrProjectType'], |
||||
this.assocMeta._tn, |
||||
this.assocMeta.columns, |
||||
this, |
||||
this.assocMeta |
||||
) : null; |
||||
}, |
||||
childPrimaryCol() { |
||||
return this.childMeta && (this.childMeta.columns.find(c => c.pv) || {})._cn |
||||
}, |
||||
childPrimaryKey() { |
||||
return this.childMeta && (this.childMeta.columns.find(c => c.pk) || {})._cn |
||||
}, |
||||
parentPrimaryKey() { |
||||
return this.meta && (this.meta.columns.find(c => c.pk) || {})._cn |
||||
}, |
||||
childQueryParams() { |
||||
if (!this.childMeta) return {} |
||||
return { |
||||
childs: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.hm).map(({hm}) => hm.tn).join()) || '', |
||||
parents: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.bt).map(({bt}) => bt.rtn).join()) || '', |
||||
many: (this.childMeta && this.childMeta.v && this.childMeta.v.filter(v => v.mm).map(({mm}) => mm.rtn).join()) || '' |
||||
} |
||||
}, |
||||
conditionGraph() { |
||||
if (!this.childMeta || !this.assocMeta) return null; |
||||
return { |
||||
[this.assocMeta.tn]: { |
||||
"relationType": "hm", |
||||
[this.assocMeta.columns.find(c => c.cn === this.mm.vcn).cn]: { |
||||
"eq": this.row[this.parentPrimaryKey] |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
childAvailableColumns() { |
||||
const hideCols = ['created_at', 'updated_at']; |
||||
if (!this.childMeta) return []; |
||||
|
||||
const columns = []; |
||||
if (this.childMeta.columns) { |
||||
columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn) && !((this.childMeta.v || []).some(v => v.bt && v.bt.cn === c.cn)))) |
||||
} |
||||
if (this.childMeta.v) { |
||||
columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1}))); |
||||
} |
||||
return columns; |
||||
}, |
||||
// todo: |
||||
form() { |
||||
return this.selectedChild ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span'; |
||||
}, |
||||
}, |
||||
watch: { |
||||
async isNew(n, o) { |
||||
if (!n && o) { |
||||
await this.saveLocalState(); |
||||
} |
||||
} |
||||
}, |
||||
created() { |
||||
this.loadChildMeta(); |
||||
this.loadAssociateTableMeta() |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.items-container { |
||||
overflow-x: visible; |
||||
max-height: min(500px, 60vh); |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
.primary-value { |
||||
.primary-key { |
||||
display: none; |
||||
margin-left: .5em; |
||||
} |
||||
|
||||
&:hover .primary-key { |
||||
display: inline; |
||||
} |
||||
} |
||||
|
||||
|
||||
.child-list-modal { |
||||
position: relative; |
||||
|
||||
.remove-child-icon { |
||||
position: absolute; |
||||
right: 10px; |
||||
top: 10px; |
||||
bottom: 10px; |
||||
opacity: 0; |
||||
} |
||||
|
||||
&:hover .remove-child-icon { |
||||
opacity: 1; |
||||
} |
||||
|
||||
} |
||||
|
||||
.child-card { |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
box-shadow: 0 0 .2em var(--v-textColor-lighten5) |
||||
} |
||||
} |
||||
|
||||
.hm-items { |
||||
//min-width: 200px; |
||||
//max-width: 400px; |
||||
flex-wrap: wrap; |
||||
row-gap: 3px; |
||||
gap: 3px; |
||||
margin: 3px auto; |
||||
} |
||||
|
||||
.chips-wrapper { |
||||
.chips { |
||||
max-width: 100%; |
||||
} |
||||
|
||||
&.active { |
||||
.chips { |
||||
max-width: calc(100% - 44px); |
||||
} |
||||
} |
||||
} |
||||
|
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,181 @@
|
||||
<template> |
||||
<div class="d-flex align-center"> |
||||
|
||||
<v-tooltip bottom> |
||||
<template #activator="{on}"> |
||||
<v-icon v-if="column.hm" color="warning" x-small class="mr-1" v-on="on">mdi-table-arrow-right</v-icon> |
||||
<v-icon v-else-if="column.bt" color="info" x-small class="mr-1" v-on="on">mdi-table-arrow-left</v-icon> |
||||
<v-icon v-else-if="column.mm" color="pink" x-small class="mr-1" v-on="on">mdi-table-network</v-icon> |
||||
|
||||
<span v-on="on" class="name flex-grow-1" :title="column._cn">{{ column._cn }}</span> |
||||
|
||||
<span v-if="column.rqd" v-on="on" class="error--text text--lighten-1"> *</span> |
||||
</template> |
||||
<span class="caption" v-html="tooltipMsg"></span> |
||||
</v-tooltip> |
||||
<v-spacer> |
||||
</v-spacer> |
||||
|
||||
<v-menu offset-y open-on-hover left> |
||||
<template v-slot:activator="{on}"> |
||||
<v-icon v-if="!isForm" v-on="on" small>mdi-menu-down</v-icon> |
||||
</template> |
||||
<v-list dense> |
||||
<v-list-item dense @click="editColumnMenu = true"> |
||||
<x-icon small class="mr-1" color="primary">mdi-pencil</x-icon> |
||||
<span class="caption">Edit</span> |
||||
</v-list-item> |
||||
<!-- <v-list-item dense @click="setAsPrimaryValue"> |
||||
<x-icon small class="mr-1" color="primary">mdi-key-star</x-icon> |
||||
<v-tooltip bottom> |
||||
<template v-slot:activator="{on}"> |
||||
<span class="caption" v-on="on">Set as Primary value</span> |
||||
</template> |
||||
<span class="caption font-weight-bold">Primary value will be shown in place of primary key</span> |
||||
</v-tooltip> |
||||
</v-list-item> --> |
||||
<v-list-item @click="columnDeleteDialog = true"> |
||||
<x-icon small class="mr-1" color="error">mdi-delete-outline</x-icon> |
||||
<span class="caption">Delete</span> |
||||
</v-list-item> |
||||
</v-list> |
||||
</v-menu> |
||||
|
||||
<v-dialog v-model="columnDeleteDialog" max-width="500" |
||||
persistent> |
||||
<v-card> |
||||
<v-card-title class="grey darken-2 subheading white--text">Confirm</v-card-title> |
||||
<v-divider></v-divider> |
||||
<v-card-text class="mt-4 title">Do you want to delete <span class="font-weight-bold">'{{ |
||||
column.cn |
||||
}}'</span> column ? |
||||
</v-card-text> |
||||
<v-divider></v-divider> |
||||
<v-card-actions class="d-flex pa-4"> |
||||
<v-spacer></v-spacer> |
||||
<v-btn small @click="columnDeleteDialog = false">Cancel</v-btn> |
||||
<v-btn small color="error" @click="deleteColumn">Confirm</v-btn> |
||||
</v-card-actions> |
||||
</v-card> |
||||
</v-dialog> |
||||
|
||||
|
||||
<v-menu offset-y v-model="editColumnMenu" content-class="elevation-0" left> |
||||
<template v-slot:activator="{on}"> |
||||
<span v-on="on"></span> |
||||
</template> |
||||
<edit-virtual-column |
||||
v-if="editColumnMenu" |
||||
v-model="editColumnMenu" |
||||
:nodes="nodes" |
||||
:edit-column="true" |
||||
:column="column" |
||||
:meta="meta" |
||||
v-on="$listeners" |
||||
></edit-virtual-column> |
||||
</v-menu> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import EditVirtualColumn from "@/components/project/spreadsheet/components/editVirtualColumn"; |
||||
|
||||
export default { |
||||
components: {EditVirtualColumn}, |
||||
props: ['column', 'nodes', 'meta', 'isForm'], |
||||
name: "virtualHeaderCell", |
||||
data: () => ({ |
||||
columnDeleteDialog: false, |
||||
editColumnMenu: false |
||||
}), |
||||
computed: { |
||||
type() { |
||||
if (this.column.bt) return 'bt' |
||||
if (this.column.hm) return 'hm' |
||||
if (this.column.mm) return 'mm' |
||||
}, |
||||
childColumn() { |
||||
if (this.column.bt) return this.column.bt.cn |
||||
if (this.column.hm) return this.column.hm.cn |
||||
if (this.column.mm) return this.column.mm.rcn |
||||
}, |
||||
childTable() { |
||||
if (this.column.bt) return this.column.bt.tn |
||||
if (this.column.hm) return this.column.hm.tn |
||||
if (this.column.mm) return this.column.mm.rtn |
||||
}, |
||||
parentTable() { |
||||
if (this.column.bt) return this.column.bt.rtn |
||||
if (this.column.hm) return this.column.hm.rtn |
||||
if (this.column.mm) return this.column.mm.tn |
||||
}, |
||||
parentColumn() { |
||||
if (this.column.bt) return this.column.bt.rcn |
||||
if (this.column.hm) return this.column.hm.rcn |
||||
if (this.column.mm) return this.column.mm.cn |
||||
}, |
||||
tooltipMsg() { |
||||
if (!this.column) return ''; |
||||
if (this.column.hm) { |
||||
return `'${this.column.hm._rtn}' has many '${this.column.hm._tn}'` |
||||
} else if (this.column.mm) { |
||||
return `'${this.column.mm._tn}' & '${this.column.mm._rtn}' have <br>many to many relation` |
||||
} else if (this.column.bt) { |
||||
return `'${this.column.bt._tn}' belongs to '${this.column.bt._rtn}'` |
||||
} |
||||
} |
||||
}, methods: { |
||||
async deleteColumn() { |
||||
try { |
||||
const column = {...this.column, cno: this.column.cn}; |
||||
await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, "xcRelationColumnDelete", { |
||||
type: this.type, |
||||
childColumn: this.childColumn, |
||||
childTable: this.childTable, |
||||
parentTable: this.parentTable, |
||||
parentColumn: this.parentColumn, |
||||
assocTable: this.column.mm && this.column.mm.vtn |
||||
}]); |
||||
this.$emit('saved'); |
||||
this.columnDeleteDialog = false; |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.name { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,203 +0,0 @@
|
||||
<template> |
||||
<v-container fluid class="wrapper"> |
||||
<v-row> |
||||
<v-col cols="6"> |
||||
<v-autocomplete |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
:loading="isRefTablesLoading" |
||||
label="Reference Table" |
||||
:full-width="false" |
||||
v-model="relation.parentTable" |
||||
:items="refTables" |
||||
item-text="_tn" |
||||
item-value="tn" |
||||
required |
||||
dense |
||||
@change="loadColumnList" |
||||
></v-autocomplete> |
||||
</v-col |
||||
> |
||||
<v-col cols="6"> |
||||
<v-autocomplete |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
:loading="isRefColumnsLoading" |
||||
label="Reference Column" |
||||
:full-width="false" |
||||
v-model="relation.parentColumn" |
||||
:items="refColumns" |
||||
item-text="_cn" |
||||
item-value="cn" |
||||
required |
||||
dense |
||||
ref="parentColumnRef" |
||||
@change="onColumnSelect" |
||||
></v-autocomplete> |
||||
</v-col |
||||
> |
||||
</v-row> |
||||
|
||||
<v-row> |
||||
<v-col cols="6"> |
||||
<v-autocomplete |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
label="On Update" |
||||
:full-width="false" |
||||
v-model="relation.onUpdate" |
||||
:items="onUpdateDeleteOptions" |
||||
required |
||||
dense |
||||
:disabled="relation.type !== 'real'" |
||||
></v-autocomplete> |
||||
</v-col> |
||||
<v-col cols="6"> |
||||
<v-autocomplete |
||||
outlined |
||||
class="caption" |
||||
hide-details |
||||
label="On Delete" |
||||
:full-width="false" |
||||
v-model="relation.onDelete" |
||||
:items="onUpdateDeleteOptions" |
||||
required |
||||
dense |
||||
:disabled="relation.type !== 'real'" |
||||
></v-autocomplete> |
||||
</v-col> |
||||
</v-row> |
||||
|
||||
|
||||
<v-row> |
||||
|
||||
<v-col> |
||||
<v-checkbox |
||||
false-value="real" |
||||
true-value="virtual" |
||||
label="Virtual Relation" |
||||
:full-width="false" |
||||
v-model="relation.type" |
||||
required |
||||
class="mt-0" |
||||
dense |
||||
></v-checkbox> |
||||
</v-col> |
||||
</v-row> |
||||
|
||||
</v-container> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "linked-to-another-options", |
||||
props: ['nodes', 'column'], |
||||
data: () => ({ |
||||
refTables: [], |
||||
refColumns: [], |
||||
relation: {}, |
||||
isRefTablesLoading: false, |
||||
isRefColumnsLoading: false, |
||||
onUpdateDeleteOptions: [ |
||||
"NO ACTION", |
||||
"CASCADE", |
||||
"RESTRICT", |
||||
"SET NULL", |
||||
"SET DEFAULT" |
||||
], |
||||
}), |
||||
async created() { |
||||
await this.loadTablesList(); |
||||
this.relation = { |
||||
childColumn: this.column.cn, |
||||
childTable: this.nodes.tn, |
||||
parentTable: this.column.rtn || "", |
||||
parentColumn: this.column.rcn || "", |
||||
onDelete: "CASCADE", |
||||
onUpdate: "CASCADE", |
||||
updateRelation: this.column.rtn ? true : false, |
||||
type: 'real' |
||||
} |
||||
}, |
||||
methods: { |
||||
async loadColumnList() { |
||||
if (!this.relation.parentTable) return; |
||||
this.isRefColumnsLoading = true; |
||||
|
||||
|
||||
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, 'columnList', {tn: this.relation.parentTable}]) |
||||
|
||||
|
||||
const columns = result.data.list; |
||||
this.refColumns = JSON.parse(JSON.stringify(columns)); |
||||
|
||||
if (this.relation.updateRelation && !this.relationColumnChanged) { |
||||
//only first time when editing add defaault value to this field |
||||
this.relation.parentColumn = this.column.rcn; |
||||
this.relationColumnChanged = true; |
||||
} else { |
||||
//find pk column and assign to parentColumn |
||||
const pkKeyColumns = this.refColumns.filter(el => el.pk); |
||||
this.relation.parentColumn = (pkKeyColumns[0] || {}).cn || ""; |
||||
} |
||||
this.onColumnSelect(); |
||||
|
||||
this.isRefColumnsLoading = false; |
||||
}, |
||||
async loadTablesList() { |
||||
this.isRefTablesLoading = true; |
||||
|
||||
|
||||
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, 'tableList']); |
||||
|
||||
|
||||
this.refTables = result.data.list.map(({tn,_tn}) => ({tn,_tn})) |
||||
this.isRefTablesLoading = false; |
||||
}, |
||||
async saveRelation() { |
||||
try { |
||||
let result = await this.$store.dispatch('sqlMgr/ActSqlOpPlus', [ |
||||
{ |
||||
env: this.nodes.env, |
||||
dbAlias: this.nodes.dbAlias |
||||
}, |
||||
this.relation.type === 'real' ? "relationCreate" : 'xcVirtualRelationCreate', |
||||
this.relation |
||||
]); |
||||
} catch (e) { |
||||
throw e; |
||||
} |
||||
}, |
||||
onColumnSelect() { |
||||
const col = this.refColumns.find(c => this.relation.parentColumn === c.cn); |
||||
this.$emit('onColumnSelect', col) |
||||
} |
||||
}, |
||||
watch: { |
||||
'column.cn': (c) => { |
||||
this.$set(this.relation, 'childColumn', c); |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
.wrapper { |
||||
border: solid 2px #7f828b33; |
||||
border-radius: 4px; |
||||
} |
||||
|
||||
/deep/ .v-input__append-inner { |
||||
margin-top: 4px !important; |
||||
} |
||||
</style> |
@ -1,101 +0,0 @@
|
||||
<template> |
||||
|
||||
<div class="d-flex align-center"> |
||||
|
||||
<div> |
||||
<div class="item" v-for="(val,i) of enumValues" :key="val"> |
||||
<input type="radio" :id="`key-radio-${val}`" class="orange--text" v-model="localState" :value="val"> |
||||
<label class="py-1 px-3 d-inline-block my-1 label" :for="`key-radio-${val}`" |
||||
:style="{ |
||||
background:colors[i % colors.length ] |
||||
}" |
||||
>{{ val }}</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import {enumColor as colors} from "@/components/project/spreadsheet/helpers/colors"; |
||||
|
||||
export default { |
||||
name: "enum-radio-editable-cell", |
||||
props: { |
||||
value: String, |
||||
column: Object |
||||
}, |
||||
mounted() { |
||||
// this.$el.focus(); |
||||
// let event; |
||||
// event = document.createEvent('MouseEvents'); |
||||
// event.initMouseEvent('mousedown', true, true, window); |
||||
// this.$el.dispatchEvent(event); |
||||
}, |
||||
computed: { |
||||
colors() { |
||||
return this.$store.state.windows.darkTheme ? colors.dark : colors.light; |
||||
}, |
||||
localState: { |
||||
get() { |
||||
return this.value |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', val); |
||||
this.$emit('update'); |
||||
} |
||||
}, |
||||
enumValues() { |
||||
if (this.column && this.column.dtxp) { |
||||
return this.column.dtxp.split(',').map(v => v.replace(/^'|'$/g, '')) |
||||
} |
||||
return []; |
||||
}, |
||||
parentListeners() { |
||||
const $listeners = {}; |
||||
|
||||
if (this.$listeners.blur) { |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if (this.$listeners.focus) { |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
.label { |
||||
border-radius: 25px; |
||||
} |
||||
|
||||
.item { |
||||
white-space: nowrap; |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,69 +0,0 @@
|
||||
<template> |
||||
<input v-on="parentListeners" v-model="localState" type="number"> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "integerCell", |
||||
props: { |
||||
value: [String, Number] |
||||
}, |
||||
mounted() { |
||||
this.$el.focus(); |
||||
}, |
||||
computed: { |
||||
localState: { |
||||
get() { |
||||
return this.value |
||||
}, |
||||
set(val) { |
||||
this.$emit('input', parseInt(val, 10)); |
||||
} |
||||
}, |
||||
parentListeners(){ |
||||
const $listeners = {}; |
||||
|
||||
if(this.$listeners.blur){ |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if(this.$listeners.focus){ |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
input { |
||||
width: 100%; |
||||
height: 100%; |
||||
color: var(--v-textColor-base); |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -1,97 +0,0 @@
|
||||
<template> |
||||
<div @keydown.stop.enter class="cell-container"> |
||||
<div class="d-flex ma-1" v-if="!isForm"> |
||||
<v-spacer> |
||||
</v-spacer> |
||||
<v-btn outlined x-small class="mr-1" @click="$emit('cancel')">Cancel</v-btn> |
||||
<v-btn x-small color="primary" @click="save">Save</v-btn> |
||||
</div> |
||||
<monaco-json-object-editor v-model="localState" |
||||
style="width: 300px;min-height: 200px;min-width:100%"></monaco-json-object-editor> |
||||
|
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import MonacoJsonEditor from "@/components/monaco/MonacoJsonEditor"; |
||||
import MonacoJsonObjectEditor from "@/components/monaco/MonacoJsonObjectEditor"; |
||||
|
||||
export default { |
||||
name: "json-cell", |
||||
components: {MonacoJsonObjectEditor, MonacoJsonEditor}, |
||||
props: { |
||||
value: String, |
||||
isForm:Boolean |
||||
}, |
||||
data: () => ({ |
||||
localState: '' |
||||
}), |
||||
created() { |
||||
this.localState = typeof this.value === 'string' ? JSON.parse(this.value) : this.value; |
||||
}, |
||||
mounted() { |
||||
}, watch: { |
||||
value(val) { |
||||
this.localState = typeof val === 'string' ? JSON.parse(val) : val; |
||||
}, |
||||
localState(val){ |
||||
if(this.isForm){ |
||||
this.$emit('input', JSON.stringify(val)) |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
save() { |
||||
this.$emit('input', JSON.stringify(this.localState)) |
||||
} |
||||
}, |
||||
computed:{ |
||||
|
||||
parentListeners(){ |
||||
const $listeners = {}; |
||||
|
||||
if(this.$listeners.blur){ |
||||
$listeners.blur = this.$listeners.blur; |
||||
} |
||||
if(this.$listeners.focus){ |
||||
$listeners.focus = this.$listeners.focus; |
||||
} |
||||
|
||||
return $listeners; |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.cell-container { |
||||
/*margin: 0 -5px;*/ |
||||
/*position: relative;*/ |
||||
width: 100% |
||||
} |
||||
</style> |
||||
<!-- |
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
--> |
@ -0,0 +1,73 @@
|
||||
export const state = () => ({ |
||||
metas: {}, |
||||
loading: {} |
||||
}); |
||||
|
||||
export const mutations = { |
||||
MutMeta(state, {key, value}) { |
||||
state.metas = {...state.metas, [key]: value}; |
||||
}, |
||||
MutLoading(state, {key, value}) { |
||||
state.loading = {...state.loading, [key]: value}; |
||||
}, |
||||
MutClear(state) { |
||||
state.metas = {}; |
||||
} |
||||
}; |
||||
|
||||
export const actions = { |
||||
async ActLoadMeta({state, commit, dispatch}, {tn, env, dbAlias, force}) { |
||||
if (!force && state.loading[tn]) { |
||||
return await new Promise(resolve => { |
||||
const unsubscribe = this.app.store.subscribe(s => { |
||||
if (s.type === 'meta/MutLoading' && s.payload.key === tn && !s.payload.value) { |
||||
unsubscribe(); |
||||
resolve(state.metas[tn]) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
if (!force && state.metas[tn]) { |
||||
return state.metas[tn]; |
||||
} |
||||
commit('MutLoading', { |
||||
key: tn, |
||||
value: true |
||||
}) |
||||
const model = await dispatch('sqlMgr/ActSqlOp', [{env, dbAlias}, 'tableXcModelGet', {tn}], {root: true}); |
||||
const meta = JSON.parse(model.meta); |
||||
commit('MutMeta', { |
||||
key: tn, |
||||
value: meta |
||||
}) |
||||
commit('MutLoading', { |
||||
key: tn, |
||||
value: undefined |
||||
}) |
||||
return force ? model : meta; |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue