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"> <script setup lang="ts">
import type { Edge, Node } from '@braks/vue-flow' 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 type { ColumnType, FormulaType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import dagre from 'dagre' import dagre from 'dagre'
@ -22,36 +22,42 @@ interface Props {
const { tables, config } = defineProps<Props>() const { tables, config } = defineProps<Props>()
console.log(tables.map((t) => t.table_name))
const { metasWithIdAsKey } = useMetas() const { metasWithIdAsKey } = useMetas()
const initialNodes = ref<Pick<Node, 'id' | 'data' | 'type'>[]>([]) const { $destroy, fitView } = useVueFlow()
const isTransitioning = ref(true)
const nodes = ref<Node[]>([]) const nodes = ref<Node[]>([])
const edges = ref<Edge[]>([]) const edges = ref<Edge[]>([])
const vueFlowKey = ref(0)
const dagreGraph = new dagre.graphlib.Graph() let dagreGraph: dagre.graphlib.Graph
dagreGraph.setDefaultEdgeLabel(() => ({})) const initDagre = () => {
dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
dagreGraph.setGraph({ rankdir: 'LR' })
}
const populateInitialNodes = () => { const populateInitialNodes = () => {
tables.forEach((table) => { nodes.value = tables.flatMap((table) => {
if (!table.id) return if (!table.id) return []
const columns = metasWithIdAsKey.value[table.id].columns!.filter( const columns =
(col) => config.showAllColumns || (!config.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord), 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 }) dagreGraph.setNode(table.id, { width: 250, height: 50 * columns.length })
initialNodes.value.push({ return [
id: table.id, {
data: { ...metasWithIdAsKey.value[table.id], showPkAndFk: config.showPkAndFk, showAllColumns: config.showAllColumns }, id: table.id,
type: 'custom', data: { ...metasWithIdAsKey.value[table.id], showPkAndFk: config.showPkAndFk, showAllColumns: config.showAllColumns },
}) type: 'custom',
position: { x: 0, y: 0 },
},
]
}) })
dagreGraph.setGraph({ rankdir: 'LR' })
} }
const populateEdges = () => { const populateEdges = () => {
@ -113,14 +119,6 @@ const populateEdges = () => {
if (source !== target) dagreGraph.setEdge(source, target) 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 { return {
id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}`, id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}`,
source: `${source}`, source: `${source}`,
@ -172,7 +170,7 @@ const layoutNodes = () => {
dagre.layout(dagreGraph) dagre.layout(dagreGraph)
nodes.value = initialNodes.value.flatMap((node) => { nodes.value = nodes.value.flatMap((node) => {
const nodeWithPosition = dagreGraph.node(node.id) const nodeWithPosition = dagreGraph.node(node.id)
if (!nodeWithPosition) return [] if (!nodeWithPosition) return []
@ -181,38 +179,60 @@ const layoutNodes = () => {
}) })
} }
onBeforeMount(() => { const init = (reset = false) => {
if (reset) {
initDagre()
}
populateInitialNodes() populateInitialNodes()
populateEdges() populateEdges()
layoutNodes() 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> </script>
<template> <template>
<VueFlow :key="vueFlowKey" :nodes="nodes" :edges="edges" :fit-view-on-init="true" :elevate-edges-on-select="true"> <Transition name="layout" mode="in-out">
<Controls class="!left-auto right-2 !top-3.5 !bottom-auto" :show-fit-view="false" :show-interactive="false" /> <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 #node-custom="props">
</template> <TableNode :data="props.data" />
</template>
<template #edge-custom="props">
<RelationEdge v-bind="props" /> <template #edge-custom="props">
</template> <RelationEdge v-bind="props" />
<Background /> </template>
<div
v-if="!config.singleTableMode" <Background />
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
> v-if="!config.singleTableMode"
<div class="flex flex-row items-center space-x-1 border-b-1 pb-1 border-gray-100"> 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"
<MdiTableLarge class="text-primary" /> style="font-size: 0.6rem"
<div>{{ $t('objects.table') }}</div> >
</div> <div class="flex flex-row items-center space-x-1 border-b-1 pb-1 border-gray-100">
<div class="flex flex-row items-center space-x-1 pt-1"> <MdiTableLarge class="text-primary" />
<MdiView class="text-primary" /> <div>{{ $t('objects.table') }}</div>
<div>{{ $t('objects.sqlVIew') }}</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>
</div> </VueFlow>
</VueFlow> </Transition>
</template> </template>

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

@ -7,7 +7,7 @@ const { table } = defineProps<{ table?: TableType }>()
const { includeM2M } = useGlobal() const { includeM2M } = useGlobal()
const { tables: projectTables } = useProject() const { tables: projectTables } = useProject()
const tables = ref<TableType>([])
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()
let isLoading = $ref(true) let isLoading = $ref(true)
@ -23,39 +23,34 @@ const config = ref({
showJunctionTableNames: false, 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) {
// if table is provided only get the table and its related tables // if table is provided only get the table and its related tables
return projectTables.value.filter( localTables = projectTables.value.filter(
(t) => (t) =>
t.id === table.id || t.id === table.id ||
table.columns?.find( table.columns?.find(
(column) => column.uidt === UITypes.LinkToAnotherRecord && column?.colOptions?.fk_related_model_id === t.id, (column) => column.uidt === UITypes.LinkToAnotherRecord && column?.colOptions?.fk_related_model_id === t.id,
), ),
) )
} else {
localTables = projectTables.value
} }
return projectTables.value await loadMetaOfTablesNotInMetas(localTables)
})
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()
isLoading = false tables.value = localTables
})
const tablesFilteredWithConfig = computed(() =>
tables.value
.filter( .filter(
(t) => (t) =>
config.value.showMMTables || config.value.showMMTables ||
@ -63,26 +58,34 @@ const tablesFilteredWithConfig = computed(() =>
// Show mm table if its the selected table // Show mm table if its the selected table
t.id === table?.id, 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( watch(
() => config.value.showAllColumns, [config, metas],
() => { async () => {
config.value.showPkAndFk = config.value.showAllColumns await populateTables()
},
{
deep: true,
}, },
) )
watch(metas, () => { watch(
erdKey.value = erdKey.value + 1 [projectTables],
}) async () => {
await populateTables()
},
{ immediate: true },
)
watch( watch(
config, () => config.value.showAllColumns,
() => { () => {
erdKey.value = erdKey.value + 1 config.value.showPkAndFk = config.value.showAllColumns
}, },
{ deep: true },
) )
</script> </script>
@ -101,7 +104,7 @@ watch(
</div> </div>
</div> </div>
<div v-else class="relative h-full"> <div v-else class="relative h-full">
<ErdSimpleView :key="erdKey" :tables="tablesFilteredWithConfig" :config="config" /> <ErdSimpleView :key="erdKey" :tables="tables" :config="config" />
<div <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" 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" :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>
<div v-if="!table" class="flex flex-row items-center"> <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" /> <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>
<div v-if="!table && showAdvancedOptions && includeM2M" class="flex flex-row items-center"> <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" /> <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>
<div v-if="showAdvancedOptions && includeM2M" class="flex flex-row items-center"> <div v-if="showAdvancedOptions && includeM2M" class="flex flex-row items-center">
<a-checkbox <a-checkbox
@ -148,7 +151,7 @@ watch(
v-e="['c:erd:showJunctionTableNames']" v-e="['c:erd:showJunctionTableNames']"
class="nc-erd-showJunctionTableNames-checkbox" 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> </div>
</div> </div>

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

@ -1,9 +1,49 @@
<script lang="ts" setup> <script lang="ts" setup>
const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const meta = inject(MetaInj) const meta = inject(MetaInj)
interface Props {
modelValue: boolean
}
const vModel = useVModel(props, 'modelValue', emits)
const selectedView = inject(ActiveViewInj)
</script> </script>
<template> <template>
<div class="w-full h-full !py-0 !px-2" style="height: 70vh"> <a-modal
<ErdView :table="meta" /> v-model:visible="vModel"
</div> 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> </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" /> <WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
<a-modal v-model:visible="showErd" size="small" :footer="null" width="max(900px,60vw)" :closable="false"> <SmartsheetToolbarErd v-model:modelValue="showErd" />
<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>
<a-modal <a-modal
v-model:visible="sharedViewListDlg" v-model:visible="sharedViewListDlg"

Loading…
Cancel
Save