Browse Source

wip(gui-v2): belongs to cell

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/2990/head
Pranav C 2 years ago
parent
commit
d2b6c12bd0
  1. 5
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  2. 8
      packages/nc-gui-v2/components/smartsheet/VirtualCell.vue
  3. 472
      packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue
  4. 15
      packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue
  5. 2
      packages/nc-gui-v2/context/index.ts

5
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isVirtualCol } from 'nocodb-sdk' import { ColumnType, isVirtualCol } from 'nocodb-sdk'
import { import {
Row,
inject, inject,
onKeyStroke, onKeyStroke,
onMounted, onMounted,
@ -13,6 +14,7 @@ import {
import { import {
ActiveViewInj, ActiveViewInj,
ChangePageInj, ChangePageInj,
EditModeInj,
FieldsInj, FieldsInj,
IsFormInj, IsFormInj,
IsGridInj, IsGridInj,
@ -279,6 +281,7 @@ const onNavigate = (dir: NavigateDir) => {
v-model="row.row[columnObj.title]" v-model="row.row[columnObj.title]"
:column="columnObj" :column="columnObj"
@navigate="onNavigate" @navigate="onNavigate"
:active="selected.col === colIndex && selected.row === rowIndex"
/> />
<SmartsheetCell <SmartsheetCell

8
packages/nc-gui-v2/components/smartsheet/VirtualCell.vue

@ -1,20 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { provide, useVirtualCell } from '#imports' import { provide, useVirtualCell } from '#imports'
import { ColumnInj, ValueInj } from '~/context' import { ColumnInj, ValueInj, ActiveCellInj } from '~/context'
import { NavigateDir } from '~/lib' import { NavigateDir } from '~/lib'
interface Props { interface Props {
column: ColumnType column: ColumnType
modelValue: any modelValue: any
active?: boolean
} }
const { column, modelValue: value } = defineProps<Props>() const props = defineProps<Props>()
const { column, modelValue: value, } = props
const active = toRef(props, 'active', false)
const emit = defineEmits(['update:modelValue', 'navigate']) const emit = defineEmits(['update:modelValue', 'navigate'])
provide(ColumnInj, column) provide(ColumnInj, column)
provide(ValueInj, value) provide(ValueInj, value)
provide(ActiveCellInj, active)
const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column) const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column)
</script> </script>

472
packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue

@ -3,6 +3,7 @@ import type { ColumnType } from 'nocodb-sdk'
import ItemChip from './components/ItemChip.vue' import ItemChip from './components/ItemChip.vue'
import { ColumnInj, ValueInj } from '~/context' import { ColumnInj, ValueInj } from '~/context'
import { useBelongsTo } from '#imports' import { useBelongsTo } from '#imports'
import MdiExpandIcon from '~icons/mdi/arrow-expand'
const column = inject(ColumnInj) const column = inject(ColumnInj)
const value = inject(ValueInj) const value = inject(ValueInj)
@ -11,480 +12,17 @@ const localState = null
const { parentMeta, loadParentMeta, primaryValueProp } = useBelongsTo(column as ColumnType) const { parentMeta, loadParentMeta, primaryValueProp } = useBelongsTo(column as ColumnType)
await loadParentMeta() await loadParentMeta()
// import ApiFactory from '@/components/project/spreadsheet/apis/apiFactory'
/* import { RelationTypes, UITypes, isSystemColumn } from 'nocodb-sdk'
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems'
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems'
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip'
import { parseIfInteger } from '@/helpers'
export default {
name: 'BelongsToCell',
components: { ListChildItems, ItemChip, ListItems },
props: {
isLocked: Boolean,
breadcrumbs: {
type: Array,
default() {
return []
},
},
isForm: Boolean,
value: [Array, Object],
meta: [Object],
nodes: [Object],
row: [Object],
api: [Object, Function],
sqlUi: [Object, Function],
active: Boolean,
isNew: Boolean,
disabledColumns: Object,
isPublic: Boolean,
metas: Object,
password: String,
column: 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,
}),
computed: {
parentMeta() {
return this.metas
? this.metas[this.column.colOptions.fk_related_model_id]
: this.$store.state.meta.metas[this.column.colOptions.fk_related_model_id]
},
// todo : optimize
parentApi() {},
parentId() {
return (
this.pid ??
(this.value &&
this.parentMeta &&
this.parentMeta.columns
.filter((c) => c.pk)
.map((c) => this.value[c.title])
.join('___'))
)
},
rowId() {
return (
this.row &&
this.meta &&
this.meta.columns
.filter((c) => c.pk)
.map((c) => this.row[c.title])
.join('___')
)
},
parentPrimaryCol() {
return this.parentMeta && (this.parentMeta.columns.find((c) => c.pv) || {}).title
},
parentPrimaryKey() {
return this.parentMeta && (this.parentMeta.columns.find((c) => c.pk) || {}).title
},
parentReferenceKey() {
return (
this.parentMeta && (this.parentMeta.columns.find((c) => c.id === this.column.colOptions.fk_parent_column_id) || {}).title
)
},
btWhereClause() {
// if parent reference key is pk, then filter out the selected value
// else, filter out the selected value + empty values (as we can't set an empty value)
const prk = this.parentReferenceKey
const selectedValue =
this.meta && this.meta.columns
? this.meta.columns
.filter((c) => c.id === this.column.colOptions.fk_child_column_id)
.map((c) => this.row[c.title] || '')
.join('___')
: ''
return `(${prk},not,${selectedValue})~or(${prk},is,null)`
},
parentQueryParams() {
if (!this.parentMeta) {
return {}
}
// todo: use reduce
return {}
},
parentAvailableColumns() {
if (!this.parentMeta) {
return []
}
const columns = []
if (this.parentMeta.columns) {
columns.push(...this.parentMeta.columns.filter((c) => !isSystemColumn(c)))
}
return columns
},
// todo:
form() {
return this.selectedParent && !this.isPublic
? () => 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]
}
return null
},
},
watch: {
isNew(n, o) {
if (!n && o) {
this.localState = null
this.$emit('update:localState', this.localState)
}
},
},
async mounted() {
if (this.isNew && this.value) {
this.localState = this.value
}
if (this.isForm) {
await this.loadParentMeta()
}
},
created() {
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.parentMeta.columns.find(
(c) =>
c.uidt === UITypes.LinkToAnotherRecord &&
c.colOptions &&
this.column.colOptions &&
c.colOptions.fk_child_column_id === this.column.colOptions.fk_child_column_id &&
c.colOptions.fk_parent_column_id === this.column.colOptions.fk_parent_column_id &&
c.colOptions.type === RelationTypes.HAS_MANY,
) || {}
).title]: [this.row],
}
this.expandFormModal = true
},
async unlink(parent) {
const column = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id)
const _cn = column.title
if (this.isNew) {
this.$emit('updateCol', this.row, _cn, null)
this.localState = null
this.$emit('update:localState', this.localState)
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.title])
.join('___')
// todo: audit
await this.$api.dbTableRow.nestedRemove(
NOCO,
this.projectName,
this.meta.title,
id,
'bt',
this.column.title,
parent[this.parentPrimaryKey],
)
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.title])
.join('___')
const _cn = this.parentMeta.columns.find((c) => c.column_name === this.hm.column_name).title
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.title])
.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,
id: this.column.colOptions.fk_related_model_id,
})
}
},
async showNewRecordModal() {
await this.loadParentMeta()
this.newRecordModal = true
},
async addChildToParent(parent) {
const pid = this._extractRowId(parent, this.parentMeta)
const id = this._extractRowId(this.row, this.meta)
const _cn = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id).title
if (this.isNew) {
const _rcn = this.parentMeta.columns.find((c) => c.id === this.column.colOptions.fk_parent_column_id).title
this.localState = parent
this.$emit('update:localState', this.localState)
this.$emit('updateCol', this.row, _cn, parent[_rcn])
this.newRecordModal = false
return
}
await this.$api.dbTableRow.nestedAdd(NOCO, this.projectName, this.meta.title, id, 'bt', this.column.title, pid)
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)
},
},
} */
</script> </script>
<template> <template>
<div class="d-flex d-100 chips-wrapper" :class="{ active }"> <div class="flex w-full chips-wrapper align-center group" :class="{ active }">
<!-- <template v-if="!isForm"> --> <div class="chips d-flex align-center flex-grow">
<div class="chips d-flex align-center img-container flex-grow-1 hm-items">
<template v-if="value || localState"> <template v-if="value || localState">
<ItemChip :active="active" :item="value" :value="value[primaryValueProp]" /> <ItemChip :active="active" :item="value" :value="value[primaryValueProp]" />
<!-- :readonly="isLocked || (isPublic && !isForm)"
@edit="editParent"
@unlink="unlink" -->
</template> </template>
</div> </div>
<!-- <div <div class="flex-1" />
v-if="!isLocked && _isUIAllowed('xcDatatableEditable') && (isForm || !isPublic)" <MdiExpandIcon class="hidden group-hover:inline text-xs text-gray-500/50 hover:text-gray-500" />
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> -->
<!-- <ListItems
v-if="newRecordModal"
:key="parentId"
v-model="newRecordModal"
:size="10"
:meta="parentMeta"
:column="column"
:primary-col="parentPrimaryCol"
:primary-key="parentPrimaryKey"
:parent-meta="meta"
:api="parentApi"
:query-params="{
...parentQueryParams,
where: isNew ? null : `${btWhereClause}`,
}"
:is-public="isPublic"
:tn="bt && bt.rtn"
:password="password"
:row-id="rowId"
@add-new-record="insertAndMapNewParentRecord"
@add="addChildToParent"
/>
<ListChildItems
v-if="parentMeta && isForm"
ref="childList"
:is-form="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})`,
}"
:bt="value"
:is-public="isPublic"
:row-id="parentId"
@new-record="showNewRecordModal"
@edit="editParent"
@unlink="unlink"
/>
<v-dialog
v-if="!isPublic && selectedParent"
v-model="expandFormModal"
:overlay-opacity="0.8"
width="1000px"
max-width="100%"
class="mx-auto"
>
<component
:is="form"
v-if="selectedParent"
ref="expandedForm"
v-model="selectedParent"
v-model:is-new="isNewParent"
:db-alias="nodes.dbAlias"
:has-many="parentMeta.hasMany"
:belongs-to="parentMeta.belongsTo"
:table="parentMeta.table_name"
:old-row="{ ...selectedParent }"
:meta="parentMeta"
:sql-ui="sqlUi"
:primary-value-column="parentPrimaryCol"
:api="parentApi"
:available-columns="parentAvailableColumns"
:nodes="nodes"
:query-params="parentQueryParams"
icon-color="warning"
:breadcrumbs="breadcrumbs"
@cancel="
selectedParent = null
expandFormModal = false
"
@input="onParentSave"
/>
</v-dialog> -->
</div> </div>
</template> </template>
<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: 0.5em;
}
&:hover .primary-key {
display: inline;
}
}
.child-card {
cursor: pointer;
&:hover {
box-shadow: 0 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>
* @author Md Ishtiaque Zafar <ishtiaque.zafar92@gmail.com>
* @author Wing-Kam Wong <wingkwong.code@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/>.
*
*/
-->

15
packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue

@ -1,25 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { ReadonlyInj } from '~/context' import { ActiveCellInj, ReadonlyInj } from '~/context'
import MdiCloseThickIcon from '~icons/mdi/close-thick' import MdiCloseThickIcon from '~icons/mdi/close-thick'
interface Props { interface Props {
value?: string | number | boolean value?: string | number | boolean
active?: boolean
item?: any item?: any
} }
const { value, active, item } = defineProps<Props>() const { value, item } = defineProps<Props>()
const readonly = inject(ReadonlyInj, false) const readonly = inject(ReadonlyInj, false)
const active = inject(ActiveCellInj, false)
</script> </script>
<template> <template>
<v-chip class="chip" :class="{ active }" small text-color="textColor"> <div class="group py-1 px-2 flex align-center gap-1 bg-gray-200/50 hover:bg-gray-200 rounded-[20px]" :class="{ active }" >
<span class="name">{{ value }}</span> <span class="name">{{ value }}</span>
<div v-show="active" v-if="!readonly" class="flex align-center ">
<div v-show="active" v-if="!readonly" class="mr-n1 ml-2"> <MdiCloseThickIcon class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" />
<MdiCloseThickIcon class="unlink-icon" />
</div> </div>
</v-chip> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

2
packages/nc-gui-v2/context/index.ts

@ -4,6 +4,8 @@ import type { EventHook } from '@vueuse/core'
import type { useViewData } from '#imports' import type { useViewData } from '#imports'
import type { TabItem } from '~/composables/useTabs' import type { TabItem } from '~/composables/useTabs'
export const EditEnabledInj: InjectionKey<boolean> = Symbol('edit-enabled')
export const ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell')
export const ColumnInj: InjectionKey<ColumnType & { meta: any }> = Symbol('column-injection') export const ColumnInj: InjectionKey<ColumnType & { meta: any }> = Symbol('column-injection')
export const MetaInj: InjectionKey<ComputedRef<TableType>> = Symbol('meta-injection') export const MetaInj: InjectionKey<ComputedRef<TableType>> = Symbol('meta-injection')
export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection') export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection')

Loading…
Cancel
Save