Browse Source

feat: M2M

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
b08a03ca0c
  1. 3
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 3
      packages/nc-gui/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions.vue
  3. 13
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  4. 18
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  5. 19
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/item-chip.vue
  6. 63
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItems.vue
  7. 2
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue
  8. 21
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  9. 21
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  10. 4
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  11. 23
      packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue
  12. 2
      packages/nc-gui/config/vuetify.options.js
  13. 4
      packages/nc-gui/plugins/globalMixin.js
  14. 2
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
  15. 1
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/KnexMigrator.ts
  16. 2
      packages/nocodb/src/lib/noco/NcProjectBuilder.ts
  17. 3
      packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts
  18. 9
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts
  19. 2
      packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
  20. 2
      packages/nocodb/src/lib/sqlMgr/SqlMgr.ts

3
packages/nc-gui/components/project/spreadsheet/components/editColumn.vue

@ -19,7 +19,7 @@
@input="newColumn.altered = newColumn.altered || 8"
:rules="[
v => !!v || 'Required',
v => !meta || !meta.columns || !column || meta.columns.every(c => column && c.cn === column.cn || v !== c.cn ) && meta.v.every(c => v !== c._cn ) || 'Duplicate column name'
v => !meta || !meta.columns || meta.columns.every(c => column && c.cn === column.cn || v !== c.cn ) && meta.v.every(c => v !== c._cn ) || 'Duplicate column name'
]"
class="caption"
label="Column name"
@ -94,6 +94,7 @@
:nodes="nodes"
:meta="meta"
:isSQLite="isSQLite"
:alias="newColumn.cn"
@onColumnSelect="onRelColumnSelect"
></linked-to-another-options>
</v-col>

3
packages/nc-gui/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions.vue

@ -103,7 +103,7 @@
<script>
export default {
name: "linked-to-another-options",
props: ['nodes', 'column', 'meta', 'isSQLite'],
props: ['nodes', 'column', 'meta', 'isSQLite','alias'],
data: () => ({
type: 'hm',
refTables: [],
@ -179,6 +179,7 @@ export default {
},
'xcM2MRelationCreate',
{
_cn:this.alias,
...this.relation,
type: this.isSQLite || this.relation.type === 'virtual' ? 'virtual' : 'real',
parentTable: this.meta.tn,

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

@ -50,8 +50,14 @@
:key="i" class="row-col my-4">
<div>
<label :for="`data-table-form-${col._cn}`" class="body-2 text-capitalize">
<span v-if="col.virtual">
</span>
<virtual-header-cell
v-if="col.virtual"
:column="col"
:nodes="nodes"
:is-form="true"
:meta="meta"
>
</virtual-header-cell>
<header-cell
v-else
:is-form="true"
@ -191,13 +197,14 @@ import EditableCell from "@/components/project/spreadsheet/components/editableCe
import dayjs from 'dayjs';
import colors from "@/mixins/colors";
import VirtualCell from "@/components/project/spreadsheet/components/virtualCell";
import VirtualHeaderCell from "@/components/project/spreadsheet/components/virtualHeaderCell";
const relativeTime = require('dayjs/plugin/relativeTime')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
dayjs.extend(relativeTime)
export default {
components: {VirtualCell, EditableCell, HeaderCell},
components: {VirtualHeaderCell, VirtualCell, EditableCell, HeaderCell},
mixins: [colors],
props: {
dbAlias: String,

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

@ -1,7 +1,7 @@
<template>
<div class="d-flex">
<div class="d-flex d-100 chips-wrapper" :class="{active}">
<template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items">
<div class="chips d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value || localState">
<item-chip
:active="active"
@ -13,7 +13,7 @@
></item-chip>
</template>
</div>
<div class=" align-center justify-center px-1 flex-shrink-1"
<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'
@ -37,6 +37,7 @@
<list-child-items
ref="childList"
v-if="parentMeta && isForm"
:isForm="isForm"
:local-state="localState? [localState] : []"
:is-new="isNew"
:size="10"
@ -329,6 +330,17 @@ export default {
margin: 3px auto;
}
.chips-wrapper{
.chips{
max-width: 100%;
}
&.active{
.chips{
max-width: calc(100% - 30px);
}
}
}
</style>
<!--
/**

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

@ -1,7 +1,13 @@
<template>
<v-chip small text-color="textColor" :color="isDark ? '' : 'primary lighten-5'"
<v-chip
class="chip"
:class="{active}"
small
text-color="textColor"
:color="isDark ? '' : 'primary lighten-5'"
@click="active && $emit('edit',item)"
>{{ value }}
>
<span class="name">{{ value }}</span>
<div v-show="active" class="mr-n1 ml-2">
<x-icon
:color="['text' , 'textLight']"
@ -25,6 +31,13 @@ export default {
}
</script>
<style scoped>
<style scoped lang="scss">
.chip {
max-width: max(100%, 60px);
.name {
text-overflow: ellipsis;
overflow: hidden;
}
}
</style>

63
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listChildItems.vue

@ -1,22 +1,44 @@
<template>
<!-- <v-dialog v-model="show" width="600">-->
<v-card width="600" color="backgroundColor">
<v-card-title class="textColor--text mx-2">{{ meta ? meta._tn : 'Children' }}
<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-btn small class="caption" color="primary" @click="$emit('new-record')">
<v-icon small>{{ bt ? 'mdi-pencil' : 'mdi-plus' }}</v-icon>&nbsp;
{{ bt ? 'Select' : 'Add' }} Record
<v-btn
small
class="caption"
color="primary"
@click="$emit('new-record')"
>
<v-icon
small>mdi-link
</v-icon>&nbsp;
Link to '{{ meta._tn }}'
</v-btn>
</v-card-title>
<v-card-text>
<div class="items-container pt-2">
<template v-if="(data && data.list && data.list.length) || (localState && localState.length)">
<div class="items-container pt-2 mb-n4">
<div class="text-right mb-2 mt-n4 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>&nbsp;
Link to '{{ meta._tn }}'
</v-btn>
</div>
<template v-if="isDataAvail">
<v-card
v-for="(ch,i) in ((data && data.list) || localState)"
class="ma-2 child-list-modal child-card"
class="mx-2 mb-2 child-list-modal child-card"
outlined
:key="i"
@click="$emit('edit',ch)"
@ -47,16 +69,27 @@
</v-card>
</template>
<div v-else-if="data" class="text-center pt-6 pb-4 textLight--text">
<div v-else-if="data || localState" class="text-center textLight--text"
:class="{'pt-6 pb-4' : !isForm , 'pt-1':isForm}">
No item{{ bt ? '' : 's' }} found
</div>
<div v-if="isForm" class="mb-2 d-flex align-center justify-center">
<pagination
v-if="!bt && isDataAvail && data && data.count > 1"
:size="size"
:count="data && data.count"
v-model="page"
@input="loadData"
></pagination>
</div>
</div>
</v-card-text>
<v-card-actions class="justify-center py-2 flex-column">
<v-card-actions v-if="!isForm" class="justify-center flex-column" :class="{'py-0':isForm}">
<pagination
v-if="!bt && data && data.list"
v-if="!bt && isDataAvail && data && data.count > 1"
:size="size"
:count="data.count"
:count="data && data.count"
v-model="page"
@input="loadData"
class="mb-3"
@ -74,6 +107,7 @@ export default {
name: "listChildItems",
components: {Pagination},
props: {
isForm: Boolean,
bt: Boolean,
localState: [Array],
isNew: Boolean,
@ -114,6 +148,9 @@ export default {
}
},
computed: {
isDataAvail() {
return (this.data && this.data.list && this.data.list.length) || (this.localState && this.localState.length);
},
show: {
set(v) {
this.$emit('input', v)

2
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue

@ -1,6 +1,6 @@
<template>
<v-dialog v-model="show" width="600">
<v-card width="600" color="backgroundColor">
<v-card width="600" >
<v-card-title class="textColor--text mx-2 justify-center">{{ title }}
</v-card-title>

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

@ -1,7 +1,7 @@
<template>
<div class="d-flex">
<div class="d-flex d-100 chips-wrapper" :class="{active}">
<template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items">
<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)"
@ -14,7 +14,7 @@
></item-chip>
</template>
</div>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<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>
@ -38,6 +38,7 @@
<list-child-items
:is="isForm ? 'list-child-items' : 'list-child-items-modal'"
:isForm="isForm"
ref="childList"
v-if="childMeta && (childListModal || isForm)"
v-model="childListModal"
@ -314,11 +315,8 @@ export default {
},
watch: {
isNew(n, o) {
debugger
if (!n && o) {
debugger
let child;
debugger
while (child = this.localState.pop()) {
this.addChildToParent(child)
}
@ -383,7 +381,16 @@ export default {
}
}
}
.chips-wrapper{
.chips{
max-width: 100%;
}
&.active{
.chips{
max-width: calc(100% - 60px);
}
}
}
</style>
<!--
/**

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

@ -1,7 +1,7 @@
<template>
<div class="d-flex">
<div class="d-flex d-100 chips-wrapper" :class="{active}">
<template v-if="!isForm">
<div class="d-flex align-center img-container flex-grow-1 hm-items">
<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"
@ -14,7 +14,7 @@
</template>
</div>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<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>
@ -38,6 +38,7 @@
<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"
@ -133,10 +134,11 @@ 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";
export default {
name: "many-to-many-cell",
components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel},
components: {ListChildItems, ItemChip, ListItems, DlgLabelSubmitCancel, listChildItemsModal },
props: {
value: [Object, Array],
meta: [Object],
@ -430,6 +432,17 @@ export default {
margin: 3px auto;
}
.chips-wrapper{
.chips{
max-width: 100%;
}
&.active{
.chips{
max-width: calc(100% - 60px);
}
}
}
</style>
<!--
/**

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

@ -18,7 +18,7 @@
<v-menu offset-y open-on-hover left>
<template v-slot:activator="{on}">
<v-icon v-on="on" small>mdi-menu-down</v-icon>
<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">
@ -78,7 +78,7 @@
import EditVirtualColumn from "@/components/project/spreadsheet/components/editVirtualColumn";
export default {
components: {EditVirtualColumn},
props: ['column', 'nodes', 'meta'],
props: ['column', 'nodes', 'meta', 'isForm'],
name: "virtualHeaderCell",
data: () => ({
columnDeleteDialog: false,

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

@ -17,7 +17,7 @@
:key="i + '_' + col._cn"
v-show="showFields[col._cn]"
@xcresize="onresize(col._cn,$event)"
@xcresizing="resizingCol = col._cn"
@xcresizing="onXcResizing(col._cn,$event)"
@xcresized="resizingCol = null"
:data-col="col._cn"
>
@ -372,6 +372,9 @@ export default {
this.meta && this.meta.columns && this.meta.columns.forEach(c => {
obj[c._cn] = columnStyling[c.uidt] && columnStyling[c.uidt].w || undefined;
})
this.meta && this.meta.v && this.meta.v.forEach(v => {
obj[v._cn] = v.bt ? '100px' : '200px';
})
Array.from(this.$el.querySelectorAll('th')).forEach(el => {
const width = el.getBoundingClientRect().width;
obj[el.dataset.col] = obj[el.dataset.col] || ((width < 100 ? 100 : width) + 'px');
@ -497,6 +500,10 @@ export default {
},
onresize(col, size) {
this.$emit('update:columnsWidth', {...this.columnsWidth, [col]: size});
},
onXcResizing(_cn, width) {
this.resizingCol = _cn;
this.resizingColWidth = width;
}
},
computed: {
@ -524,7 +531,7 @@ export default {
style() {
let style = '';
for (const [key, val] of Object.entries(this.columnsWidth || {})) {
if (val && key !== this.resizingCol)
if (val && key !== this.resizingCol) {
style += `
[data-col="${key}"]{
min-width: ${val};
@ -532,13 +539,23 @@ export default {
width: ${val};
}
`;
} else if (key === this.resizingCol) {
style += `
[data-col="${key}"]{
min-width: ${this.resizingColWidth};
max-width: ${this.resizingColWidth};
width: ${this.resizingColWidth};
}
`;
}
}
return style;
}
},
},
data: () => ({
resizingCol: null,
resizingColWidth: null,
selectedExpandRowIndex: null,
selectedExpandRowMeta: null,
addNewColMenu: false,

2
packages/nc-gui/config/vuetify.options.js

@ -20,7 +20,7 @@ export default function ({app}) {
textColor: '#ffffff',
text: '#ffffff',
textLight: '#b3b3b3',
backgroundColor: '#969696',
backgroundColor: '#565656',
backgroundColorDefault: '#1f1f1f'
},
light: {

4
packages/nc-gui/plugins/globalMixin.js

@ -49,6 +49,8 @@ export default async ({store}) => {
el.appendChild(resizer);
resizer.addEventListener('mousedown', initDrag, false);
el.style.transition = '20ms width0';
let startX, startY, startWidth;
function initDrag(e) {
@ -75,7 +77,7 @@ export default async ({store}) => {
function doDrag(e) {
width = (startWidth + e.clientX - startX) + 'px';
el.style.maxWidth = el.style.minWidth = el.style.width = width;
emit(vnode, 'xcresizing');
emit(vnode, 'xcresizing', width);
//
// p.style.height = (startHeight + e.clientY - startY) + 'px';
}

2
packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts

@ -1453,7 +1453,7 @@ class BaseModelSql extends BaseModel {
const gs = _.groupBy(parents, this.dbModels[parent]?.columnToAlias?.[rcn] || rcn);
childs.forEach(row => {
row[this.dbModels?.[parent]?._tn || parent] = gs[row[this.dbModels[parent]?.columnToAlias?.[cn] || cn]]?.[0] || gs[row[cn || gs[row[this.dbModels[parent]?.columnToAlias?.[cn] || cn]]?.[0]]]?.[0];
row[this.dbModels?.[parent]?._tn || parent] = gs[row[this?.columnToAlias?.[cn] || cn]]?.[0];
})
}

1
packages/nocodb/src/lib/migrator/SqlMigrator/lib/KnexMigrator.ts

@ -1077,7 +1077,6 @@ export default class KnexMigrator extends SqlMigrator {
// if (NcConfigFactory.hasDbUrl()) {
// this.project = NcConfigFactory.make();
// }
// console.log(this.project);
return await this._migrationsUp({

2
packages/nocodb/src/lib/noco/NcProjectBuilder.ts

@ -92,7 +92,7 @@ export default class NcProjectBuilder {
});
break;
case 'xcM2MRelationCreate':
curBuilder.onManyToManyRelationCreate(data.req.args.parentTable, data.req.args.childTable, data.req.args);
await curBuilder.onManyToManyRelationCreate(data.req.args.parentTable, data.req.args.childTable, data.req.args);
break;
case 'relationCreate':

3
packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

@ -55,6 +55,7 @@ export default class NcMetaIOImpl extends NcMetaIO {
private connection: XKnex;
// todo: need to fix
private trx: Knex.Transaction;
@ -539,8 +540,10 @@ export default class NcMetaIOImpl extends NcMetaIO {
}
async startTransaction() {
if (!this.trx) {
this.trx = await this.connection.transaction();
}
}
async metaReset(project_id: string, dbAlias: string, apiType?: string): Promise<void> {
// const apiType: string = this.config?.envs?.[this.config.env || this.config.workingEnv]?.db.find(d => {

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

@ -2455,8 +2455,10 @@ export default class NcMetaMgr {
}
// todo : transaction
// todo : transaction in sql client
protected async xcRelationColumnDelete(args: any, req, deleteColumn = true): Promise<any> {
// this.xcMeta.startTransaction();
// try {
const dbAlias = this.getDbAlias(args);
const projectId = this.getProjectId(args);
@ -2584,6 +2586,11 @@ export default class NcMetaMgr {
}
break;
}
// this.xcMeta.commit()
// } catch (e) {
// this.xcMeta.rollback(e)
// throw e;
// }
}

2
packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts

@ -1190,7 +1190,7 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
this.log('Within relation delete event - \'%s\' ==> \'%s\'', tnp, tnc)
this.deleteRoutesForTables([tnp, tnc])
const relations = await this.getRelationList();
const relations = await this.getXcRelationList();
{
const hasMany = this.extractHasManyRelationsOfTable(relations, tnp);

2
packages/nocodb/src/lib/sqlMgr/SqlMgr.ts

@ -101,7 +101,7 @@ export default class SqlMgr {
private project_id: any;
private _project: any;
// @ts-ignore
private _migrator: any;
private _migrator: KnexMigrator;
// @ts-ignore
private logsRef: any;
private currentProjectJson: any;

Loading…
Cancel
Save