Browse Source

feat(nc-gui): Fixed the rendering hack and some cleanup

pull/3612/head
Muhammed Mustafa 2 years ago
parent
commit
83fb0717e6
  1. 128
      packages/nc-gui/components/erd/SimpleView.vue
  2. 81
      packages/nc-gui/components/erd/View.vue
  3. 46
      packages/nc-gui/components/smartsheet-toolbar/Erd.vue
  4. 15
      packages/nc-gui/components/smartsheet-toolbar/ViewActions.vue

128
packages/nc-gui/components/erd/SimpleView.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Edge, Node } from '@braks/vue-flow'
import { Background, Controls, VueFlow } from '@braks/vue-flow'
import { Background, Controls, VueFlow, useVueFlow } from '@braks/vue-flow'
import type { ColumnType, FormulaType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import dagre from 'dagre'
@ -22,36 +22,42 @@ interface Props {
const { tables, config } = defineProps<Props>()
console.log(tables.map((t) => t.table_name))
const { metasWithIdAsKey } = useMetas()
const initialNodes = ref<Pick<Node, 'id' | 'data' | 'type'>[]>([])
const { $destroy, fitView } = useVueFlow()
const isTransitioning = ref(true)
const nodes = ref<Node[]>([])
const edges = ref<Edge[]>([])
const vueFlowKey = ref(0)
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
let dagreGraph: dagre.graphlib.Graph
const initDagre = () => {
dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
dagreGraph.setGraph({ rankdir: 'LR' })
}
const populateInitialNodes = () => {
tables.forEach((table) => {
if (!table.id) return
nodes.value = tables.flatMap((table) => {
if (!table.id) return []
const columns = metasWithIdAsKey.value[table.id].columns!.filter(
(col) => config.showAllColumns || (!config.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord),
)
const columns =
metasWithIdAsKey.value[table.id].columns?.filter(
(col) => config.showAllColumns || (!config.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord),
) || []
dagreGraph.setNode(table.id, { width: 250, height: 50 * columns.length })
initialNodes.value.push({
id: table.id,
data: { ...metasWithIdAsKey.value[table.id], showPkAndFk: config.showPkAndFk, showAllColumns: config.showAllColumns },
type: 'custom',
})
return [
{
id: table.id,
data: { ...metasWithIdAsKey.value[table.id], showPkAndFk: config.showPkAndFk, showAllColumns: config.showAllColumns },
type: 'custom',
position: { x: 0, y: 0 },
},
]
})
dagreGraph.setGraph({ rankdir: 'LR' })
}
const populateEdges = () => {
@ -113,14 +119,6 @@ const populateEdges = () => {
if (source !== target) dagreGraph.setEdge(source, target)
// todo: In the case of one self relation and one has many between 2 tables in only single table view, edges are getting messed up
if (source === target) {
// rerender after 200ms
setTimeout(() => {
vueFlowKey.value = 1
}, 350)
}
return {
id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}`,
source: `${source}`,
@ -172,7 +170,7 @@ const layoutNodes = () => {
dagre.layout(dagreGraph)
nodes.value = initialNodes.value.flatMap((node) => {
nodes.value = nodes.value.flatMap((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
if (!nodeWithPosition) return []
@ -181,38 +179,60 @@ const layoutNodes = () => {
})
}
onBeforeMount(() => {
const init = (reset = false) => {
if (reset) {
initDagre()
}
populateInitialNodes()
populateEdges()
layoutNodes()
if (reset) {
setTimeout(() => fitView({ duration: 300 }))
}
}
initDagre()
onBeforeMount(init)
onScopeDispose($destroy)
watch([() => tables, () => config], () => init(true), { deep: true, flush: 'pre' })
useEventListener('transitionend', () => {
isTransitioning.value = false
})
</script>
<template>
<VueFlow :key="vueFlowKey" :nodes="nodes" :edges="edges" :fit-view-on-init="true" :elevate-edges-on-select="true">
<Controls class="!left-auto right-2 !top-3.5 !bottom-auto" :show-fit-view="false" :show-interactive="false" />
<template #node-custom="props">
<TableNode :data="props.data" />
</template>
<template #edge-custom="props">
<RelationEdge v-bind="props" />
</template>
<Background />
<div
v-if="!config.singleTableMode"
class="absolute bottom-0 right-0 flex flex-col text-xs bg-white px-2 py-1 border-1 rounded-md border-gray-200 z-50 nc-erd-histogram"
style="font-size: 0.6rem"
>
<div class="flex flex-row items-center space-x-1 border-b-1 pb-1 border-gray-100">
<MdiTableLarge class="text-primary" />
<div>{{ $t('objects.table') }}</div>
</div>
<div class="flex flex-row items-center space-x-1 pt-1">
<MdiView class="text-primary" />
<div>{{ $t('objects.sqlVIew') }}</div>
<Transition name="layout" mode="in-out">
<VueFlow v-if="!isTransitioning" :nodes="nodes" :edges="edges" fit-view-on-init elevate-edges-on-select>
<Controls class="!left-auto right-2 !top-3.5 !bottom-auto" :show-fit-view="false" :show-interactive="false" />
<template #node-custom="props">
<TableNode :data="props.data" />
</template>
<template #edge-custom="props">
<RelationEdge v-bind="props" />
</template>
<Background />
<div
v-if="!config.singleTableMode"
class="absolute bottom-0 right-0 flex flex-col text-xs bg-white px-2 py-1 border-1 rounded-md border-gray-200 z-50 nc-erd-histogram"
style="font-size: 0.6rem"
>
<div class="flex flex-row items-center space-x-1 border-b-1 pb-1 border-gray-100">
<MdiTableLarge class="text-primary" />
<div>{{ $t('objects.table') }}</div>
</div>
<div class="flex flex-row items-center space-x-1 pt-1">
<MdiView class="text-primary" />
<div>{{ $t('objects.sqlVIew') }}</div>
</div>
</div>
</div>
</VueFlow>
</VueFlow>
</Transition>
</template>

81
packages/nc-gui/components/erd/View.vue

@ -7,7 +7,7 @@ const { table } = defineProps<{ table?: TableType }>()
const { includeM2M } = useGlobal()
const { tables: projectTables } = useProject()
const tables = ref<TableType>([])
const { metas, getMeta } = useMetas()
let isLoading = $ref(true)
@ -23,39 +23,34 @@ const config = ref({
showJunctionTableNames: false,
})
const tables = computed(() => {
const loadMetaOfTablesNotInMetas = async (localTables: TableType[]) => {
await Promise.all(
localTables
.filter((table) => !metas.value[table.id!])
.map(async (table) => {
await getMeta(table.id!)
}),
)
}
const populateTables = async () => {
let localTables: TableType[] = []
if (table) {
// if table is provided only get the table and its related tables
return projectTables.value.filter(
localTables = projectTables.value.filter(
(t) =>
t.id === table.id ||
table.columns?.find(
(column) => column.uidt === UITypes.LinkToAnotherRecord && column?.colOptions?.fk_related_model_id === t.id,
),
)
} else {
localTables = projectTables.value
}
return projectTables.value
})
const loadMetaOfTablesNotInMetas = async () => {
await Promise.all(
tables.value
.filter((table) => !metas.value[table.id!])
.map(async (table) => {
await getMeta(table.id!)
}),
)
}
onMounted(async () => {
await loadMetaOfTablesNotInMetas()
await loadMetaOfTablesNotInMetas(localTables)
isLoading = false
})
const tablesFilteredWithConfig = computed(() =>
tables.value
tables.value = localTables
.filter(
(t) =>
config.value.showMMTables ||
@ -63,26 +58,34 @@ const tablesFilteredWithConfig = computed(() =>
// Show mm table if its the selected table
t.id === table?.id,
)
.filter((t) => (!config.value.showViews && t.type !== 'view') || config.value.showViews),
)
.filter((t) => (!config.value.showViews && t.type !== 'view') || config.value.showViews)
isLoading = false
}
watch(
() => config.value.showAllColumns,
() => {
config.value.showPkAndFk = config.value.showAllColumns
[config, metas],
async () => {
await populateTables()
},
{
deep: true,
},
)
watch(metas, () => {
erdKey.value = erdKey.value + 1
})
watch(
[projectTables],
async () => {
await populateTables()
},
{ immediate: true },
)
watch(
config,
() => config.value.showAllColumns,
() => {
erdKey.value = erdKey.value + 1
config.value.showPkAndFk = config.value.showAllColumns
},
{ deep: true },
)
</script>
@ -101,7 +104,7 @@ watch(
</div>
</div>
<div v-else class="relative h-full">
<ErdSimpleView :key="erdKey" :tables="tablesFilteredWithConfig" :config="config" />
<ErdSimpleView :key="erdKey" :tables="tables" :config="config" />
<div
class="absolute top-2 right-10 flex-col bg-white py-2 px-4 border-1 border-gray-100 rounded-md z-50 space-y-1 nc-erd-context-menu z-50"
@ -132,15 +135,15 @@ watch(
}"
:disabled="!config.showAllColumns"
/>
<span class="ml-2 select-none" style="font-size: 0.65rem">{{ $t('activity.erd.showPkAndFk') }}</span>
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showPkAndFk') }}</span>
</div>
<div v-if="!table" class="flex flex-row items-center">
<a-checkbox v-model:checked="config.showViews" v-e="['c:erd:showViews']" class="nc-erd-showViews-checkbox" />
<span class="ml-2 select-none" style="font-size: 0.65rem">{{ $t('activity.erd.showSqlViews') }}</span>
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showSqlViews') }}</span>
</div>
<div v-if="!table && showAdvancedOptions && includeM2M" class="flex flex-row items-center">
<a-checkbox v-model:checked="config.showMMTables" v-e="['c:erd:showMMTables']" class="nc-erd-showMMTables-checkbox" />
<span class="ml-2 select-none" style="font-size: 0.65rem">{{ $t('activity.erd.showMMTables') }}</span>
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showMMTables') }}</span>
</div>
<div v-if="showAdvancedOptions && includeM2M" class="flex flex-row items-center">
<a-checkbox
@ -148,7 +151,7 @@ watch(
v-e="['c:erd:showJunctionTableNames']"
class="nc-erd-showJunctionTableNames-checkbox"
/>
<span class="ml-2 select-none" style="font-size: 0.65rem">{{ $t('activity.erd.showJunctionTableNames') }}</span>
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showJunctionTableNames') }}</span>
</div>
</div>
</div>

46
packages/nc-gui/components/smartsheet-toolbar/Erd.vue

@ -1,9 +1,49 @@
<script lang="ts" setup>
const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const meta = inject(MetaInj)
interface Props {
modelValue: boolean
}
const vModel = useVModel(props, 'modelValue', emits)
const selectedView = inject(ActiveViewInj)
</script>
<template>
<div class="w-full h-full !py-0 !px-2" style="height: 70vh">
<ErdView :table="meta" />
</div>
<a-modal
v-model:visible="vModel"
size="small"
:footer="null"
width="max(900px,60vw)"
:closable="false"
wrap-class-name="erd-single-table-modal"
>
<div class="flex flex-row justify-between w-full items-center mb-1">
<a-typography-title class="ml-4 select-none" type="secondary" :level="5">
{{ `${$t('title.erdView')}: ${selectedView?.title}` }}
</a-typography-title>
<a-button type="text" class="!rounded-md border-none -mt-1.5 -mr-1" @click="vModel = false">
<template #icon>
<MdiClose class="cursor-pointer mt-1 nc-modal-close" />
</template>
</a-button>
</div>
<div class="w-full h-full !py-0 !px-2" style="height: 70vh">
<ErdView :table="meta" />
</div>
</a-modal>
</template>
<style lang="scss">
.erd-single-table-modal {
.ant-modal {
transform: none !important;
}
}
</style>

15
packages/nc-gui/components/smartsheet-toolbar/ViewActions.vue

@ -230,20 +230,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
<a-modal v-model:visible="showErd" size="small" :footer="null" width="max(900px,60vw)" :closable="false">
<div class="flex flex-row justify-between w-full items-center mb-1">
<a-typography-title class="ml-4 select-none" type="secondary" :level="5">
{{ `${$t('title.erdView')}: ${selectedView?.title}` }}
</a-typography-title>
<a-button type="text" class="!rounded-md border-none -mt-1.5 -mr-1" @click="showErd = false">
<template #icon>
<MdiClose class="cursor-pointer mt-1 nc-modal-close" />
</template>
</a-button>
</div>
<SmartsheetToolbarErd />
</a-modal>
<SmartsheetToolbarErd v-model:modelValue="showErd" />
<a-modal
v-model:visible="sharedViewListDlg"

Loading…
Cancel
Save