Browse Source

feat: relations in unsaved row

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
280ced9d06
  1. 1
      packages/nc-gui/components/project/appStore/inputs/dateTimePickerCell.vue
  2. 2
      packages/nc-gui/components/project/spreadsheet/components/editableCell/dateTimePickerCell.vue
  3. 46
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  4. 31
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  5. 15
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/item-chip.vue
  6. 145
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  7. 70
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  8. 81
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  9. 11
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  10. 29
      packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue
  11. 7
      packages/nc-gui/mixins/device.js
  12. 2
      packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts

1
packages/nc-gui/components/project/appStore/inputs/dateTimePickerCell.vue

@ -30,7 +30,6 @@ export default {
return new Date(+this.value); return new Date(+this.value);
} }
return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : this.value; return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : this.value;
}, },
set(val) { set(val) {

2
packages/nc-gui/components/project/spreadsheet/components/editableCell/dateTimePickerCell.vue

@ -35,7 +35,7 @@ export default {
} }
return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : this.value; return /\dT\d/.test(this.value) ? new Date(this.value.replace(/(\d)T(?=\d)/, '$1 ')) : new Date(this.value);
}, },
set(val) { set(val) {
// if(/^\d{6,}$/.test(this.value)){ // if(/^\d{6,}$/.test(this.value)){

46
packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue

@ -41,9 +41,12 @@
<v-row class="h-100"> <v-row class="h-100">
<v-col class="h-100 px-10" style="overflow-y: auto" cols="8" :offset="isNew || !toggleDrawer ? 2 : 0"> <v-col class="h-100 px-10" style="overflow-y: auto" cols="8" :offset="isNew || !toggleDrawer ? 2 : 0">
<div :class="{ <div
'active-row' : active === col._cn v-for="(col,i) in fields"
}" v-for="(col,i) in fields" :class="{
'active-row' : active === col._cn,
required: isRequired(col, localState)
}"
:key="i" class="row-col my-4"> :key="i" class="row-col my-4">
<div> <div>
<label :for="`data-table-form-${col._cn}`" class="body-2 text-capitalize"> <label :for="`data-table-form-${col._cn}`" class="body-2 text-capitalize">
@ -71,6 +74,7 @@
:sql-ui="sqlUi" :sql-ui="sqlUi"
@loadTableData="reload" @loadTableData="reload"
:is-new="isNew" :is-new="isNew"
@updateCol="updateCol"
></virtual-cell> ></virtual-cell>
<div <div
@ -264,6 +268,20 @@ export default {
}, },
}, },
methods: { methods: {
isRequired(_columnObj, rowObj) {
let columnObj = _columnObj;
if (columnObj.bt) {
columnObj = this.meta.columns.find(c => c.cn === columnObj.bt.cn);
}
return (columnObj.rqd
&& (rowObj[columnObj._cn] === undefined || rowObj[columnObj._cn] === null)
&& !columnObj.default);
},
updateCol(row, _cn, pid) {
this.$set(row, _cn, pid)
this.$set(this.changedColumns, _cn, true)
},
isYou(email) { isYou(email) {
return this.$store.state.users.user && this.$store.state.users.user.email === email; return this.$store.state.users.user && this.$store.state.users.user.email === email;
}, },
@ -274,13 +292,6 @@ export default {
model_name: this.meta._tn model_name: this.meta._tn
}]) }])
this.logs = data.list; this.logs = data.list;
// this.$nextTick(() => {
// const objDiv = this.$refs.commentsList.$el;
// if (objDiv) {
// objDiv.scrollTop = objDiv.scrollHeight;
// }
// })
this.loadingLogs = false; this.loadingLogs = false;
}, },
async save() { async save() {
@ -294,6 +305,7 @@ export default {
if (this.isNew) { if (this.isNew) {
const data = await this.api.insert(updatedObj); const data = await this.api.insert(updatedObj);
Object.assign(this.localState, data) Object.assign(this.localState, data)
await this.reload();
} else { } else {
if (Object.keys(updatedObj).length) { if (Object.keys(updatedObj).length) {
await this.api.update(id, updatedObj, this.oldRow); await this.api.update(id, updatedObj, this.oldRow);
@ -302,6 +314,7 @@ export default {
} }
} }
this.$emit('update:oldRow', {...this.localState}) this.$emit('update:oldRow', {...this.localState})
this.changedColumns = {}; this.changedColumns = {};
this.$emit('input', this.localState); this.$emit('input', this.localState);
@ -315,7 +328,7 @@ export default {
}, },
async reload() { async reload() {
// const id = this.meta.columns.filter((c) => c.pk).map(c => this.localState[c._cn]).join('___'); // const id = this.meta.columns.filter((c) => c.pk).map(c => this.localState[c._cn]).join('___');
const where = this.meta.columns.filter((c) => c.pk).map(c => `(${c._cn},eq,${this.localState[c._cn]})`).join(''); const where = this.meta.columns.filter((c) => c.pk).map(c => `(${c._cn},eq,${this.localState[c._cn]})`).join('~and');
this.$set(this, 'changedColumns', {}); this.$set(this, 'changedColumns', {});
// this.localState = await this.api.read(id); // this.localState = await this.api.read(id);
this.localState = (await this.api.list({...(this.queryParams || {}), where}) || [{}])[0]; this.localState = (await this.api.list({...(this.queryParams || {}), where}) || [{}])[0];
@ -359,7 +372,7 @@ export default {
} else { } else {
return (this.meta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn))) || []; return (this.meta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn))) || [];
} }
} },
} }
} }
</script> </script>
@ -469,6 +482,15 @@ h5 {
.comment-box.focus { .comment-box.focus {
border: 1px solid #4185f4; border: 1px solid #4185f4;
} }
.required > div > label + *{
border:1px solid red;
border-radius: 4px;
min-height: 42px;
display: flex;
align-items: center;
justify-content: flex-end;
background: var(--v-backgroundColorDefault-base);
}
</style> </style>
<!-- <!--
/** /**

31
packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue

@ -1,19 +1,18 @@
<template> <template>
<div class="d-flex"> <div class="d-flex">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <div class="d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value"> <template v-if="value || localState">
<item-chip <item-chip
:active="active" :active="active"
:item="value" :item="value"
:color="colors[i%colors.length]" :value="Object.values(value || localState)[1]"
:value="Object.values(value)[1]"
:key="i" :key="i"
@edit="editParent" @edit="editParent"
@unlink="unlink" @unlink="unlink"
></item-chip> ></item-chip>
</template> </template>
</div> </div>
<div v-if="!isNew" class=" align-center justify-center px-1 flex-shrink-1" <div class=" align-center justify-center px-1 flex-shrink-1"
:class="{'d-none': !active, 'd-flex':active }"> :class="{'d-none': !active, 'd-flex':active }">
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{ <x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{
value ? 'mdi-arrow-expand' : 'mdi-plus' value ? 'mdi-arrow-expand' : 'mdi-plus'
@ -72,7 +71,6 @@
</template> </template>
<script> <script>
import colors from "@/mixins/colors";
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory";
import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems"; import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems";
import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip"; import ItemChip from "@/components/project/spreadsheet/components/virtualCell/components/item-chip";
@ -80,7 +78,6 @@ import ItemChip from "@/components/project/spreadsheet/components/virtualCell/co
export default { export default {
name: "belongs-to-cell", name: "belongs-to-cell",
components: {ItemChip, ListItems}, components: {ItemChip, ListItems},
mixins: [colors],
props: { props: {
value: [Object, Array], value: [Object, Array],
meta: [Object], meta: [Object],
@ -105,6 +102,7 @@ export default {
selectedParent: null, selectedParent: null,
isNewParent: false, isNewParent: false,
expandFormModal: false, expandFormModal: false,
localState: null,
}), }),
methods: { methods: {
@ -125,11 +123,16 @@ export default {
async unlink() { async unlink() {
const column = this.meta.columns.find(c => c.cn === this.bt.cn); 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) { if (column.rqd) {
this.$toast.info('Unlink is not possible, instead map to another parent.').goAway(3000) this.$toast.info('Unlink is not possible, instead map to another parent.').goAway(3000)
return return
} }
const _cn = column._cn;
const id = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); 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) await this.api.update(id, {[_cn]: null}, this.row)
this.$emit('loadTableData') this.$emit('loadTableData')
@ -177,9 +180,16 @@ export default {
// this.list = await this.parentApi.paginatedList({}) // this.list = await this.parentApi.paginatedList({})
}, },
async addParentToChild(parent) { async addParentToChild(parent) {
const pid = this.parentMeta.columns.filter((c) => c.pk).map(c => parent[c._cn]).join('___'); 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 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; 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)
this.newRecordModal = false;
return
}
await this.api.update(id, { await this.api.update(id, {
[_cn]: pid [_cn]: pid
@ -237,6 +247,13 @@ export default {
form() { form() {
return this.selectedParent ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span'; return this.selectedParent ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span';
}, },
},
watch: {
isNew(n, o) {
if (!n && o) {
this.localState = null
}
}
} }
} }
</script> </script>

15
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/item-chip.vue

@ -1,8 +1,8 @@
<template> <template>
<v-chip small :color="color" <v-chip small text-color="textColor" :color="isDark ? '' : 'primary lighten-5'"
@click="active && $emit('edit',item)" @click="active && $emit('edit',item)"
>{{value}} >{{ value }}
<div v-show="active" class="mr-n1 ml-2 mt-n1"> <div v-show="active" class="mr-n1 ml-2">
<x-icon <x-icon
:color="['text' , 'textLight']" :color="['text' , 'textLight']"
x-small x-small
@ -16,11 +16,10 @@
<script> <script>
export default { export default {
props:{ props: {
color:String, value: String,
value:String, active: Boolean,
active:Boolean, item: Object
item:Object
}, },
name: "item-chip" name: "item-chip"
} }

145
packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue

@ -1,12 +1,11 @@
<template> <template>
<div class="d-flex"> <div class="d-flex">
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <div class="d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value"> <template v-if="value||localState">
<item-chip <item-chip
v-for="(ch,i) in value" v-for="(ch,i) in (value|| localState)"
:active="active" :active="active"
:item="ch" :item="ch"
:color="colors[i%colors.length]"
:value="Object.values(ch)[1]" :value="Object.values(ch)[1]"
:key="i" :key="i"
@edit="editChild" @edit="editChild"
@ -32,7 +31,7 @@
@add="addChildToParent" @add="addChildToParent"
:query-params="{ :query-params="{
...childQueryParams, ...childQueryParams,
where: `~not(${childForeignKey},eq,${parentId})~or(${childForeignKey},is,null)`, where: isNew ? null :`~not(${childForeignKey},eq,${parentId})~or(${childForeignKey},is,null)`,
}"/> }"/>
<list-child-items <list-child-items
@ -55,71 +54,6 @@
@delete="deleteChild" @delete="deleteChild"
/> />
<!--<v-dialog v-if="childListModal" v-model="childListModal" width="600">
<v-card width="600" color="backgroundColor">
<v-card-title class="textColor&#45;&#45;text mx-2">{{ childMeta ? childMeta._tn : 'Children' }}
<v-spacer>
</v-spacer>
<v-btn small class="caption" color="primary" @click="showNewRecordModal">
<v-icon small>mdi-plus</v-icon>&nbsp;
Add Record
</v-btn>
</v-card-title>
<v-card-text>
<div class="items-container">
<template v-if="childList">
<v-card
v-for="(ch,i) in childList.list"
class="ma-2 child-list-modal child-card"
outlined
:key="i"
@click="editChild(ch)"
>
<div class="remove-child-icon d-flex align-center">
<x-icon
:tooltip="`Unlink this '${childMeta._tn}' from '${meta._tn}'`"
:color="['error','grey']"
small
@click.stop="unlinkChild(ch,i)"
icon.class="mr-1 mt-n1"
>mdi-link-variant-remove
</x-icon>
<x-icon
:tooltip="`Delete row in '${childMeta._tn}'`"
:color="['error','grey']"
small
@click.stop="deleteChild(ch,i)"
>mdi-delete-outline
</x-icon>
</div>
<v-card-title class="primary-value textColor&#45;&#45;text text&#45;&#45;lighten-2">
{{ ch[childPrimaryCol] }}
<span class="grey&#45;&#45;text caption primary-key"
v-if="childPrimaryKey">(Primary Key : {{ ch[childPrimaryKey] }})</span>
</v-card-title>
</v-card>
</template>
</div>
</v-card-text>
<v-card-actions class="justify-center py-2 flex-column">
<pagination
v-if="childList"
:size="childListPagination.size"
:count="childList.count"
v-model="childListPagination.page"
@input="showChildListModal"
class="mb-3"
></pagination>
</v-card-actions>
</v-card>
</v-dialog>
-->
<dlg-label-submit-cancel <dlg-label-submit-cancel
type="primary" type="primary"
v-if="dialogShow" v-if="dialogShow"
@ -143,7 +77,7 @@
:has-many="childMeta.hasMany" :has-many="childMeta.hasMany"
:belongs-to="childMeta.belongsTo" :belongs-to="childMeta.belongsTo"
@cancel="selectedChild = null" @cancel="selectedChild = null"
@input="$emit('loadTableData');loadChildList();" @input="$emit('loadTableData')"
:table="childMeta.tn" :table="childMeta.tn"
v-model="selectedChild" v-model="selectedChild"
:old-row="{...selectedChild}" :old-row="{...selectedChild}"
@ -166,7 +100,6 @@
</template> </template>
<script> <script>
import colors from "@/mixins/colors";
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory";
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel"; import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel";
import Pagination from "@/components/project/spreadsheet/components/pagination"; import Pagination from "@/components/project/spreadsheet/components/pagination";
@ -183,7 +116,6 @@ export default {
Pagination, Pagination,
DlgLabelSubmitCancel DlgLabelSubmitCancel
}, },
mixins: [colors],
props: { props: {
value: [Object, Array], value: [Object, Array],
meta: [Object], meta: [Object],
@ -198,41 +130,20 @@ export default {
newRecordModal: false, newRecordModal: false,
childListModal: false, childListModal: false,
childMeta: null, childMeta: null,
// list: null,
// childList: null,
dialogShow: false, dialogShow: false,
confirmAction: null, confirmAction: null,
confirmMessage: '', confirmMessage: '',
selectedChild: null, selectedChild: null,
expandFormModal: false, expandFormModal: false,
// listPagination: { isNewChild: false,
// page: 1, localState: []
// size: 10
// },
// childListPagination: {
// page: 1,
// size: 10
// },
isNewChild: false
}), }),
methods: { methods: {
async showChildListModal() { async showChildListModal() {
await this.loadChildMeta(); await this.loadChildMeta();
// await this.loadChildList();
this.childListModal = true; this.childListModal = true;
}, },
// async loadChildList() {
// const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___');
// const _cn = this.childMeta.columns.find(c => c.cn === this.hm.cn)._cn;
// this.childList = await this.childApi.paginatedList({
// where: `(${_cn},eq,${pid})`,
// limit: this.childListPagination.size,
// offset: this.childListPagination.size * (this.childListPagination.page - 1),
// ...this.childQueryParams
// })
//
// },
async deleteChild(child) { async deleteChild(child) {
this.dialogShow = true; this.dialogShow = true;
this.confirmMessage = this.confirmMessage =
@ -249,20 +160,13 @@ export default {
if (this.childListModal && this.$refs.childList) { if (this.childListModal && this.$refs.childList) {
this.$refs.childList.loadData(); this.$refs.childList.loadData();
} }
}catch (e) { } catch (e) {
this.$toast.error(e.message) this.$toast.error(e.message)
} }
} }
} }
}, },
async unlinkChild(child) { async unlinkChild(child) {
// this.dialogShow = true;
// this.confirmMessage =
// 'Do you want to unlink the record?';
// this.confirmAction = async act => {
// if (act === 'hideDialog') {
// this.dialogShow = false;
// } else {
await this.loadChildMeta(); await this.loadChildMeta();
const column = this.childMeta.columns.find(c => c.cn === this.hm.cn); const column = this.childMeta.columns.find(c => c.cn === this.hm.cn);
if (column.rqd) { if (column.rqd) {
@ -297,6 +201,12 @@ export default {
this.newRecordModal = true; this.newRecordModal = true;
}, },
async addChildToParent(child) { 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 id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
const _cn = this.childForeignKey; const _cn = this.childForeignKey;
this.newRecordModal = false; this.newRecordModal = false;
@ -383,6 +293,16 @@ export default {
parentId() { parentId() {
return this.meta && this.meta.columns ? this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___') : ''; 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) {
let child;
while (child = this.localState.pop()) {
this.addChildToParent(child)
}
}
}
} }
} }
</script> </script>
@ -405,25 +325,6 @@ export default {
} }
} }
/*
.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 { .child-card {
cursor: pointer; cursor: pointer;

70
packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue

@ -3,11 +3,10 @@
<div class="d-flex align-center img-container flex-grow-1 hm-items"> <div class="d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value"> <template v-if="(value || localState)">
<item-chip v-for="(v,j) in value" <item-chip v-for="(v,j) in (value || localState)"
:active="active" :active="active"
:item="v" :item="v"
:color="colors[j%colors.length]"
:value="Object.values(v)[2]" :value="Object.values(v)[2]"
:key="j" :key="j"
@edit="editChild" @edit="editChild"
@ -54,47 +53,6 @@
@edit="editChild" @edit="editChild"
@unlink="unlinkChild" @unlink="unlinkChild"
/> />
<!--<v-dialog v-if="childListModal" v-model="childListModal" width="600">
<v-card width="600" color="backgroundColor">
<v-card-title class="textColor&#45;&#45;text mx-2">{{ childMeta ? childMeta._tn : 'Children' }}</v-card-title>
<v-card-text>
<div class="items-container">
<template v-if="childList">
<v-card
v-for="(ch,i) in childList.list"
class="ma-2 child-list-modal child-card"
outlined
:key="i"
@click="editChild(ch)"
>
<x-icon
class="remove-child-icon"
:color="['error','grey']"
small
@click.stop="removeChild(ch,i)"
>mdi-delete-outline
</x-icon>
<v-card-title class="primary-value textColor&#45;&#45;text text&#45;&#45;lighten-2">{{ ch[childPrimaryCol] }}
<span class="grey&#45;&#45;text caption primary-key"
v-if="childPrimaryKey">(Primary Key : {{ ch[childPrimaryKey] }})</span>
</v-card-title>
</v-card>
</template>
</div>
</v-card-text>
<v-card-actions class="justify-center pb-6">
<v-btn small outlined class="caption" color="primary" @click="showNewRecordModal">
<v-icon>mdi-plus</v-icon>
Add Record
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
-->
<v-dialog <v-dialog
:overlay-opacity="0.8" :overlay-opacity="0.8"
v-if="selectedChild" v-if="selectedChild"
@ -167,7 +125,6 @@
</template> </template>
<script> <script>
import colors from "@/mixins/colors";
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory"; import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory";
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel"; import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel";
import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems"; import ListItems from "@/components/project/spreadsheet/components/virtualCell/components/listItems";
@ -176,7 +133,6 @@ import ListChildItems from "@/components/project/spreadsheet/components/virtualC
export default { export default {
name: "many-to-many-cell", name: "many-to-many-cell",
mixins: [colors],
components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel}, components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel},
props: { props: {
value: [Object, Array], value: [Object, Array],
@ -195,13 +151,13 @@ export default {
childListModal: false, childListModal: false,
childMeta: null, childMeta: null,
assocMeta: null, assocMeta: null,
// list: null,
childList: null, childList: null,
dialogShow: false, dialogShow: false,
confirmAction: null, confirmAction: null,
confirmMessage: '', confirmMessage: '',
selectedChild: null, selectedChild: null,
expandFormModal: false expandFormModal: false,
localState: []
}), }),
methods: { methods: {
@ -262,7 +218,7 @@ export default {
}, },
async loadAssociateTableMeta() { async loadAssociateTableMeta() {
// todo: optimize // todo: optimize
if (!this.childMeta) { if (!this.assocMeta) {
const assocTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ const assocTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
env: this.nodes.env, env: this.nodes.env,
dbAlias: this.nodes.dbAlias dbAlias: this.nodes.dbAlias
@ -278,6 +234,12 @@ export default {
// this.list = await this.childApi.paginatedList({}) // this.list = await this.childApi.paginatedList({})
}, },
async addChildToParent(child) { 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 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 pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___');
@ -384,6 +346,16 @@ export default {
form() { form() {
return this.selectedChild ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span'; return this.selectedChild ? () => import("@/components/project/spreadsheet/components/expandedForm") : 'span';
}, },
},
watch: {
isNew(n, o) {
if (!n && o) {
let child;
while (child = this.localState.pop()) {
this.addChildToParent(child)
}
}
}
} }
} }
</script> </script>

81
packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue

@ -0,0 +1,81 @@
<template>
<div class="d-flex align-center">
<v-icon v-if="column.hm" color="warning" x-small class="mr-1">mdi-table-arrow-right</v-icon>
<v-icon v-else-if="column.bt" color="info" x-small class="mr-1">mdi-table-arrow-left</v-icon>
<v-icon v-else-if="column.mm" color="pink" x-small class="mr-1">mdi-table-network</v-icon>
{{ column._cn }}
<span v-if="column.rqd" class="error--text text--lighten-1">&nbsp;*</span>
<v-spacer>
</v-spacer>
<v-menu offset-y open-on-hover left>
<template v-slot:activator="{on}">
<v-icon 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>
</div>
</template>
<script>
export default {
props: ['column'],
name: "virtualHeaderCell",
data: () => ({
}),
}
</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/>.
*
*/
-->

11
packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue

@ -687,25 +687,26 @@ export default {
} }
}, },
async save() { async save() {
for (let row = 0; row < this.rowLength; row++) { for (let row = 0; row < this.rowLength; row++) {
const {row: rowObj, rowMeta} = this.data[row]; const {row: rowObj, rowMeta} = this.data[row];
if (rowMeta.new) { if (rowMeta.new) {
try { try {
const pks = this.availableColumns.filter((col) => { const pks = this.meta.columns.filter((col) => {
return col.pk; return col.pk;
}); });
if (this.availableColumns.every((col) => { if (this.meta.columns.every((col) => {
return !col.ai; return !col.ai;
}) && pks.length && pks.every(col => !rowObj[col._cn])) { }) && pks.length && pks.every(col => !rowObj[col._cn])) {
return this.$toast.info('Primary column is empty please provide some value').goAway(3000); return this.$toast.info('Primary column is empty please provide some value').goAway(3000);
} }
if (this.availableColumns.some((col) => { if (this.meta.columns.some((col) => {
return col.rqd && (rowObj[col._cn] === undefined || rowObj[col._cn] === null) && !col.default return !col.ai && col.rqd && (rowObj[col._cn] === undefined || rowObj[col._cn] === null) && !col.default
})) { })) {
return; return;
} }
const insertObj = this.availableColumns.reduce((o, col) => { const insertObj = this.meta.columns.reduce((o, col) => {
if (!col.ai && (rowObj && rowObj[col._cn]) !== null) { if (!col.ai && (rowObj && rowObj[col._cn]) !== null) {
o[col._cn] = rowObj && rowObj[col._cn]; o[col._cn] = rowObj && rowObj[col._cn];
} }

29
packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue

@ -25,7 +25,10 @@
<!-- :style="columnsWidth[col._cn] ? `min-width:${columnsWidth[col._cn]}; max-width:${columnsWidth[col._cn]}` : ''" <!-- :style="columnsWidth[col._cn] ? `min-width:${columnsWidth[col._cn]}; max-width:${columnsWidth[col._cn]}` : ''"
--> -->
<template v-if="col.virtual">{{ col._cn }}</template> <virtual-header-cell v-if="col.virtual"
:column="col"
/>
<header-cell <header-cell
v-else v-else
@ -113,7 +116,7 @@
'active' : !isPublicView && selected.col === col && selected.row === row && isEditable , 'active' : !isPublicView && selected.col === col && selected.row === row && isEditable ,
'primary-column' : primaryValueColumn === columnObj._cn, 'primary-column' : primaryValueColumn === columnObj._cn,
'text-center': isCentrallyAligned(columnObj), 'text-center': isCentrallyAligned(columnObj),
'required':columnObj.rqd && (rowObj[columnObj._cn]===undefined || rowObj[columnObj._cn]===null) && !columnObj.default 'required': isRequired(columnObj,rowObj)
}" }"
@dblclick="makeEditable(col,row,columnObj.ai)" @dblclick="makeEditable(col,row,columnObj.ai)"
@click="makeSelected(col,row);" @click="makeSelected(col,row);"
@ -130,6 +133,8 @@
:active="selected.col === col && selected.row === row" :active="selected.col === col && selected.row === row"
:sql-ui="sqlUi" :sql-ui="sqlUi"
v-on="$listeners" v-on="$listeners"
:is-new="rowMeta.new"
@updateCol="(...args) => updateCol(...args, columnObj.bt && meta.columns.find( c => c.cn === columnObj.bt.cn), col, row)"
></virtual-cell> ></virtual-cell>
<!-- <!--
@ -313,9 +318,13 @@ import HasManyCell from "@/components/project/spreadsheet/components/virtualCell
import BelongsToCell from "@/components/project/spreadsheet/components/virtualCell/belogsToCell"; import BelongsToCell from "@/components/project/spreadsheet/components/virtualCell/belogsToCell";
import ManyToMany from "@/components/project/spreadsheet/components/virtualCell/manyToManyCell"; import ManyToMany from "@/components/project/spreadsheet/components/virtualCell/manyToManyCell";
import VirtualCell from "@/components/project/spreadsheet/components/virtualCell"; import VirtualCell from "@/components/project/spreadsheet/components/virtualCell";
import VirtualHeaderCell from "@/components/project/spreadsheet/components/virtualHeaderCell";
export default { export default {
components: {VirtualCell, ManyToMany, BelongsToCell, HasManyCell, TableCell, EditColumn, EditableCell, HeaderCell}, components: {
VirtualHeaderCell,
VirtualCell, ManyToMany, BelongsToCell, HasManyCell, TableCell, EditColumn, EditableCell, HeaderCell
},
mixins: [colors], mixins: [colors],
props: { props: {
relationType: String, relationType: String,
@ -341,6 +350,20 @@ export default {
this.calculateColumnWidth(); this.calculateColumnWidth();
}, },
methods: { methods: {
isRequired(_columnObj, rowObj) {
let columnObj = _columnObj;
if (columnObj.bt) {
columnObj = this.meta.columns.find(c => c.cn === columnObj.bt.cn);
}
return (columnObj.rqd
&& (rowObj[columnObj._cn] === undefined || rowObj[columnObj._cn] === null)
&& !columnObj.default);
},
updateCol(row, column, value, columnObj, colIndex, rowIndex) {
this.$set(row, column, value);
this.onCellValueChange(colIndex, rowIndex, columnObj)
},
calculateColumnWidth() { calculateColumnWidth() {
setTimeout(() => { setTimeout(() => {
const obj = {}; const obj = {};

7
packages/nc-gui/mixins/device.js

@ -11,6 +11,12 @@ export default {
} }
}, },
computed: { computed: {
isDark() {
return this.$vuetify && this.$vuetify.theme && this.$vuetify.theme.dark;
},
isLight() {
return this.$vuetify && this.$vuetify.theme && this.$vuetify.theme.light;
},
language() { language() {
// const dummy = new Date(); // const dummy = new Date();
@ -33,6 +39,7 @@ export default {
&& this.$route.path && this.$route.path
&& (this.$route.path === '/nc' || this.$route.path === '/nc/' || this.$route.path.startsWith('/nc/')); && (this.$route.path === '/nc' || this.$route.path === '/nc/' || this.$route.path.startsWith('/nc/'));
}, },
_meta() { _meta() {
return this._isMac ? '⌘' : '^'; return this._isMac ? '⌘' : '^';
}, },

2
packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts

@ -1597,7 +1597,7 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
} }
meta.v = [ meta.v = [
...meta.v.filter(vc => (vc.bt && meta.manyToMany.some(mm => vc.bt.rtn === mm.vtn)) || vc.mm), ...meta.v.filter(vc => !(vc.hm && meta.manyToMany.some(mm => vc.hm.tn === mm.vtn))),
// todo: ignore existing m2m relations // todo: ignore existing m2m relations
...meta.manyToMany.map(mm => { ...meta.manyToMany.map(mm => {

Loading…
Cancel
Save