Browse Source

Merge pull request #4071 from nocodb/feat/erd-color-scale

pull/4127/head
Braks 2 years ago committed by GitHub
parent
commit
183cf085f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      packages/nc-gui/assets/style.scss
  2. 1
      packages/nc-gui/components/dashboard/settings/AppStore.vue
  3. 2
      packages/nc-gui/components/dashboard/settings/Erd.vue
  4. 59
      packages/nc-gui/components/erd/ConfigPanel.vue
  5. 106
      packages/nc-gui/components/erd/Flow.vue
  6. 19
      packages/nc-gui/components/erd/HistogramPanel.vue
  7. 199
      packages/nc-gui/components/erd/RelationEdge.vue
  8. 189
      packages/nc-gui/components/erd/TableNode.vue
  9. 124
      packages/nc-gui/components/erd/View.vue
  10. 225
      packages/nc-gui/components/erd/utils.ts
  11. 16
      packages/nc-gui/components/general/Tooltip.vue
  12. 36
      packages/nc-gui/components/smartsheet/toolbar/Erd.vue
  13. 644
      packages/nc-gui/package-lock.json
  14. 4
      packages/nc-gui/package.json

6
packages/nc-gui/assets/style.scss

@ -280,4 +280,8 @@ a {
.nc-toolbar-btn {
@apply !shadow-none rounded hover:(ring-1 ring-primary ring-opacity-100) focus:(ring-1 ring-accent ring-opacity-100);
}
}
.ant-modal {
@apply !top-[50px];
}

1
packages/nc-gui/components/dashboard/settings/AppStore.vue

@ -77,6 +77,7 @@ onMounted(async () => {
min-height="300"
:footer="null"
wrap-class-name="nc-modal-plugin-install"
v-bind="$attrs"
>
<LazyDashboardSettingsAppInstall
v-if="pluginApp && showPluginInstallModal"

2
packages/nc-gui/components/dashboard/settings/Erd.vue

@ -1,5 +1,5 @@
<template>
<div class="w-full h-full !py-0 !px-2" style="height: 70vh">
<div class="w-full h-full !p-0 h-70vh">
<ErdView />
</div>
</template>

59
packages/nc-gui/components/erd/ConfigPanel.vue

@ -0,0 +1,59 @@
<script lang="ts" setup>
import { Panel } from '@vue-flow/additional-components'
import type { ERDConfig } from './utils'
import { ref, useGlobal, useVModel } from '#imports'
const props = defineProps<{
config: ERDConfig
}>()
const { includeM2M } = useGlobal()
const config = useVModel(props, 'config')
const showAdvancedOptions = ref(false)
</script>
<template>
<Panel class="flex flex-col bg-white border-1 rounded border-gray-200 z-50 px-3 py-1 nc-erd-context-menu" position="top-right">
<div class="flex items-center gap-2">
<a-checkbox v-model:checked="config.showAllColumns" v-e="['c:erd:showAllColumns']" class="nc-erd-showColumns-checkbox" />
<span class="select-none nc-erd-showColumns-label" style="font-size: 0.65rem" @dblclick="showAdvancedOptions = true">
{{ $t('activity.erd.showColumns') }}
</span>
</div>
<div class="flex items-center gap-2">
<a-checkbox
v-model:checked="config.showPkAndFk"
v-e="['c:erd:showPkAndFk']"
class="nc-erd-showPkAndFk-checkbox"
:class="[
`nc-erd-showPkAndFk-checkbox-${config.showAllColumns ? 'enabled' : 'disabled'}`,
`nc-erd-showPkAndFk-checkbox-${config.showPkAndFk ? 'checked' : 'unchecked'}`,
]"
:disabled="!config.showAllColumns"
/>
<span class="select-none text-[0.65rem]">{{ $t('activity.erd.showPkAndFk') }}</span>
</div>
<div v-if="!config.singleTableMode" class="flex items-center gap-2">
<a-checkbox v-model:checked="config.showViews" v-e="['c:erd:showViews']" class="nc-erd-showViews-checkbox" />
<span class="select-none text-[0.65rem]">{{ $t('activity.erd.showSqlViews') }}</span>
</div>
<div v-if="!config.singleTableMode && 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 text-[0.65rem]">{{ $t('activity.erd.showMMTables') }}</span>
</div>
<div v-if="showAdvancedOptions && includeM2M" class="flex items-center gap-2">
<a-checkbox
v-model:checked="config.showJunctionTableNames"
v-e="['c:erd:showJunctionTableNames']"
class="nc-erd-showJunctionTableNames-checkbox"
/>
<span class="select-none text-[0.65rem]">{{ $t('activity.erd.showJunctionTableNames') }}</span>
</div>
</Panel>
</template>

106
packages/nc-gui/components/erd/Flow.vue

@ -1,66 +1,108 @@
<script setup lang="ts">
import { Background, Controls } from '@vue-flow/additional-components'
import { Background, Controls, Panel } from '@vue-flow/additional-components'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import type { TableType } from 'nocodb-sdk'
import type { ErdFlowConfig } from './utils'
import type { ERDConfig } from './utils'
import { useErdElements } from './utils'
import { onScopeDispose, toRefs, watch } from '#imports'
import { computed, onScopeDispose, toRefs, watch } from '#imports'
interface Props {
tables: TableType[]
config: ErdFlowConfig
config: ERDConfig
}
const props = defineProps<Props>()
const { tables, config } = toRefs(props)
const { $destroy, fitView, onPaneReady } = useVueFlow({ minZoom: 0.1, maxZoom: 2 })
const { $destroy, fitView, onPaneReady, viewport, onNodeDoubleClick } = useVueFlow({ minZoom: 0.05, maxZoom: 2 })
const { layout, elements } = useErdElements(tables, config)
const showSkeleton = computed(() => viewport.value.zoom < 0.15)
function init() {
layout()
layout(showSkeleton.value)
if (!showSkeleton.value) {
setTimeout(zoomIn, 100)
}
}
function zoomIn(nodeId?: string) {
fitView({ nodes: nodeId ? [nodeId] : undefined, duration: 300, minZoom: 0.2 })
}
onPaneReady(() => {
layout(showSkeleton.value)
setTimeout(() => {
fitView({ duration: 500 })
fitView({ duration: 250, minZoom: 0.16 })
}, 100)
}
})
onPaneReady(init)
onNodeDoubleClick(({ node }) => {
if (showSkeleton.value) zoomIn()
watch([() => tables, () => config], init, { deep: true, flush: 'post' })
setTimeout(() => {
zoomIn(node.id)
}, 250)
})
watch(tables, init)
watch(showSkeleton, (isSkeleton) => {
layout(isSkeleton)
setTimeout(() => {
fitView({
duration: 300,
minZoom: isSkeleton ? undefined : viewport.value.zoom,
maxZoom: isSkeleton ? viewport.value.zoom : undefined,
})
}, 100)
})
onScopeDispose($destroy)
</script>
<template>
<VueFlow v-model="elements" elevate-edges-on-select>
<Controls position="top-right" :show-fit-view="false" :show-interactive="false" />
<VueFlow v-model="elements">
<Controls class="rounded" position="bottom-left" :show-fit-view="false" :show-interactive="false" />
<template #node-custom="{ data }">
<ErdTableNode :data="data" />
<template #node-custom="{ data, dragging }">
<ErdTableNode :data="data" :dragging="dragging" :show-skeleton="showSkeleton" />
</template>
<template #edge-custom="edgeProps">
<ErdRelationEdge v-bind="edgeProps" />
<ErdRelationEdge v-bind="edgeProps" :show-skeleton="showSkeleton" />
</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">
<MdiEyeCircleOutline class="text-primary" />
<div>{{ $t('objects.sqlVIew') }}</div>
</div>
</div>
<Background :size="showSkeleton ? 2 : undefined" :gap="showSkeleton ? 50 : undefined" />
<Transition name="layout">
<Panel
v-if="showSkeleton && config.showAllColumns"
position="bottom-center"
class="color-transition z-5 cursor-pointer rounded shadow-sm text-slate-400 font-semibold px-4 py-2 bg-slate-100/50 hover:(text-slate-900 ring ring-accent ring-opacity-100 bg-slate-100/90)"
@click="zoomIn"
>
<!-- todo: i18n -->
Zoom in to view columns
</Panel>
</Transition>
<slot />
</VueFlow>
</template>
<style>
.vue-flow__edges {
z-index: 1000 !important;
}
.vue-flow__controls-zoomin {
@apply rounded-t;
}
.vue-flow__controls-zoomout {
@apply rounded-b;
}
</style>

19
packages/nc-gui/components/erd/HistogramPanel.vue

@ -0,0 +1,19 @@
<script lang="ts" setup>
import { Panel } from '@vue-flow/additional-components'
</script>
<template>
<Panel class="text-xs bg-white border-1 rounded border-gray-200 z-50 nc-erd-histogram" position="bottom-right">
<div class="flex flex-col divide-y-1">
<div class="flex items-center gap-1 p-2">
<MdiTableLarge class="text-primary" />
<div>{{ $t('objects.table') }}</div>
</div>
<div class="flex items-center gap-1 p-2">
<MdiEyeCircleOutline class="text-primary" />
<div>{{ $t('objects.sqlVIew') }}</div>
</div>
</div>
</Panel>
</template>

199
packages/nc-gui/components/erd/RelationEdge.vue

@ -2,9 +2,10 @@
import type { EdgeProps, Position } from '@vue-flow/core'
import { EdgeText, getBezierPath } from '@vue-flow/core'
import type { CSSProperties } from '@vue/runtime-dom'
import type { EdgeData } from './utils'
import { computed, toRef } from '#imports'
interface RelationEdgeProps extends EdgeProps {
interface RelationEdgeProps extends EdgeProps<EdgeData> {
id: string
sourceX: number
sourceY: number
@ -12,36 +13,41 @@ interface RelationEdgeProps extends EdgeProps {
targetY: number
sourcePosition: Position
targetPosition: Position
data: {
isManyToMany: boolean
isSelfRelation: boolean
label: string
}
markerEnd: string
data: EdgeData
style: CSSProperties
targetHandleId: string
selected?: boolean
showSkeleton: boolean
markerEnd: string
events: EdgeProps['events']
sourceNode: EdgeProps['sourceNode']
targetNode: EdgeProps['targetNode']
}
const props = defineProps<RelationEdgeProps>()
const baseStroke = 2
const data = toRef(props, 'data')
const isHovering = ref(false)
const edgePath = computed(() => {
if (data.value.isSelfRelation) {
const { sourceX, sourceY, targetX, targetY } = props
const radiusX = (sourceX - targetX) * 0.6
const radiusY = 50
return `M ${sourceX} ${sourceY} A ${radiusX} ${radiusY} 0 1 0 ${targetX} ${targetY}`
return [`M ${sourceX} ${sourceY} A ${radiusX} ${radiusY} 0 1 0 ${targetX} ${targetY}`]
}
return getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
})
return getBezierPath({ ...props })
})
props.events?.mouseEnter?.(() => {
isHovering.value = true
})
props.events?.mouseLeave?.(() => {
isHovering.value = false
})
</script>
@ -52,72 +58,95 @@ export default {
</script>
<template>
<path
:id="id"
:style="style"
class="path-wrapper p-4 hover:cursor-pointer"
:stroke-width="8"
fill="none"
:d="edgePath[0]"
:marker-end="markerEnd"
/>
<path
:id="id"
:style="style"
class="path stroke-gray-500 hover:stroke-green-500 hover:cursor-pointer"
:stroke-width="1.5"
fill="none"
:d="edgePath[0]"
:marker-end="markerEnd"
/>
<EdgeText
v-if="data.label?.length > 0"
:class="`nc-erd-table-label-${data.label.toLowerCase().replace(' ', '-').replace('\(', '').replace(')', '')}`"
:x="edgePath[1]"
:y="edgePath[2]"
:label="data.label"
:label-style="{ fill: 'white' }"
:label-show-bg="true"
:label-bg-style="{ fill: '#10b981' }"
:label-bg-padding="[2, 4]"
:label-bg-border-radius="2"
/>
<rect
class="nc-erd-edge-rect"
:x="sourceX"
:y="sourceY - 4"
width="8"
height="8"
fill="#fff"
stroke="#6F3381"
:stroke-width="1.5"
:transform="`rotate(45,${sourceX + 2},${sourceY - 4})`"
/>
<rect
v-if="data.isManyToMany"
class="nc-erd-edge-rect"
:x="targetX"
:y="targetY - 4"
width="8"
height="8"
fill="#fff"
stroke="#6F3381"
:stroke-width="1.5"
:transform="`rotate(45,${targetX + 2},${targetY - 4})`"
/>
<circle v-else class="nc-erd-edge-circle" :cx="targetX" :cy="targetY" fill="#fff" :r="5" stroke="#6F3381" :stroke-width="1.5" />
</template>
<defs>
<linearGradient id="linear-gradient" x1="-28.83" y1="770.92" x2="771.05" y2="-28.95" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#06b6d4" />
<stop offset="0.18" stop-color="#155e75" />
<stop offset="0.49" stop-color="#84cc16" />
<stop offset="0.88" stop-color="#10b981" />
<stop offset="0.99" stop-color="#047857" />
</linearGradient>
</defs>
<style scoped lang="scss">
.path-wrapper:hover + .path {
@apply stroke-green-500;
stroke-width: 2;
}
.path:hover {
stroke-width: 2;
}
</style>
<Transition name="layout" :duration="50" mode="in-out">
<path
v-if="selected || isHovering"
style="stroke: url(#linear-gradient)"
:stroke-width="(showSkeleton ? baseStroke * 12 : baseStroke * 8) / (selected || isHovering ? 2 : 1)"
fill="none"
:d="edgePath[0]"
:marker-end="showSkeleton ? markerEnd : ''"
/>
<path
v-else
:id="id"
class="stroke-slate-500"
:style="style"
:stroke-width="showSkeleton ? baseStroke * 4 : baseStroke"
fill="none"
:d="edgePath[0]"
:marker-end="showSkeleton ? markerEnd : ''"
/>
</Transition>
<path class="opacity-0" :stroke-width="showSkeleton ? baseStroke * 12 : baseStroke * 8" fill="none" :d="edgePath[0]" />
<Transition name="layout">
<EdgeText
v-if="data.label?.length > 0"
:key="`edge-text-${id}.${showSkeleton}`"
class="color-transition"
:class="[
selected || isHovering ? 'opacity-100' : 'opacity-0 !pointer-events-none',
showSkeleton ? '!text-6xl' : '!text-xs',
`nc-erd-table-label-${data.label.toLowerCase().replace(' ', '-').replace('\(', '').replace(')', '')}`,
]"
:x="edgePath[1]"
:y="edgePath[2]"
:label="showSkeleton ? data.simpleLabel : data.label"
:label-style="{ fill: 'white', fontSize: `${showSkeleton ? baseStroke * 2 : baseStroke / 2}rem` }"
:label-show-bg="true"
:label-bg-style="{ fill: data.color }"
:label-bg-padding="[8, 6]"
:label-bg-border-radius="2"
/>
</Transition>
<template v-if="!showSkeleton">
<rect
class="nc-erd-edge-rect"
:x="sourceX"
:y="sourceY - 4"
:width="8"
:height="8"
fill="#fff"
:stroke="sourceNode.data.color"
:stroke-width="2"
:transform="`rotate(45,${sourceX + 2},${sourceY - 4})`"
/>
<rect
v-if="data.isManyToMany"
class="nc-erd-edge-rect"
:x="targetX"
:y="targetY - 4"
:width="8"
:height="8"
fill="#fff"
:stroke="sourceNode.data.color"
:stroke-width="2"
:transform="`rotate(45,${targetX + 2},${targetY - 4})`"
/>
<circle
v-else
class="nc-erd-edge-circle"
:cx="targetX"
:cy="targetY"
fill="#fff"
:r="5"
:stroke="targetNode.data.color"
:stroke-width="2"
/>
</template>
</template>

189
packages/nc-gui/components/erd/TableNode.vue

@ -1,125 +1,132 @@
<script lang="ts" setup>
import type { NodeProps } from '@vue-flow/core'
import { Handle, Position } from '@vue-flow/core'
import type { ColumnType, TableType } from 'nocodb-sdk'
import { Handle, Position, useVueFlow } from '@vue-flow/core'
import type { LinkToAnotherRecordType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { MetaInj, computed, provide, toRefs, useNuxtApp } from '#imports'
import type { NodeData } from './utils'
import { MetaInj, computed, provide, refAutoReset, toRef, useNuxtApp, watch } from '#imports'
interface Props extends NodeProps {
data: TableType & { showPkAndFk: boolean; showAllColumns: boolean }
interface Props extends NodeProps<NodeData> {
data: NodeData
showSkeleton: boolean
dragging: boolean
}
const props = defineProps<Props>()
const { data, showSkeleton, dragging } = defineProps<Props>()
const { data } = toRefs(props)
const { viewport } = useVueFlow()
provide(MetaInj, data as Ref<TableType>)
const table = toRef(data, 'table')
const isZooming = refAutoReset(false, 200)
provide(MetaInj, table)
const { $e } = useNuxtApp()
const columns = computed(() => {
// Hide hm ltar created for `mm` relations
return data.value.columns?.filter((col) => !(col.uidt === UITypes.LinkToAnotherRecord && col.system === 1))
})
const pkAndFkColumns = computed(() => {
return columns.value
?.filter(() => data.value.showPkAndFk && data.value.showAllColumns)
.filter((col) => col.pk || col.uidt === UITypes.ForeignKey)
})
const nonPkColumns = computed(() => {
return columns.value
?.filter(
(col: ColumnType) => data.value.showAllColumns || (!data.value.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord),
)
.filter((col: ColumnType) => !col.pk && col.uidt !== UITypes.ForeignKey)
})
const relatedColumnId = (col: Record<string, any>) =>
col.colOptions.type === 'mm' ? col.colOptions.fk_parent_column_id : col.colOptions.fk_child_column_id
const relatedColumnId = (colOptions: LinkToAnotherRecordType | any) =>
colOptions.type === 'mm' ? colOptions.fk_parent_column_id : colOptions.fk_child_column_id
const hasColumns = computed(() => data.pkAndFkColumns.length || data.nonPkColumns.length)
watch(
() => viewport.value.zoom,
() => {
isZooming.value = true
},
)
</script>
<template>
<div
class="h-full flex flex-col min-w-16 bg-gray-50 rounded-lg border-1 nc-erd-table-node"
:class="`nc-erd-table-node-${data.table_name}`"
@click="$e('c:erd:node-click')"
<GeneralTooltip
class="h-full flex flex-1 justify-center items-center"
:modifier-key="showSkeleton || viewport.zoom > 0.35 ? 'Alt' : undefined"
:disabled="dragging || isZooming"
>
<GeneralTooltip modifier-key="Alt">
<template #title> {{ data.table_name }} </template>
<template #title>
<div class="capitalize">{{ table.table_name }}</div>
</template>
<div
class="relative h-full flex flex-col justify-center bg-slate-50 min-w-16 min-h-8 rounded-lg nc-erd-table-node"
:class="[
`nc-erd-table-node-${table.table_name}`,
showSkeleton ? 'cursor-pointer items-center bg-slate-200 min-h-200px min-w-300px px-4' : '',
]"
@click="$e('c:erd:node-click')"
>
<div
class="text-gray-600 text-md py-2 border-b-1 border-gray-200 rounded-t-lg w-full pr-3 pl-2 bg-gray-100 font-semibold flex flex-row items-center"
:class="[showSkeleton ? '' : 'bg-primary bg-opacity-10', hasColumns ? 'border-b-1' : '']"
class="text-slate-600 text-md py-2 border-slate-500 rounded-t-lg w-full h-full px-3 font-semibold flex items-center"
>
<MdiTableLarge v-if="data.type === 'table'" class="text-primary" />
<MdiEyeCircleOutline v-else class="text-primary" />
<div class="flex pl-1.5">
{{ data.title }}
<MdiTableLarge v-if="table.type === 'table'" class="text-primary" :class="showSkeleton ? 'text-6xl !px-2' : ''" />
<MdiEyeCircleOutline v-else class="text-primary" :class="showSkeleton ? 'text-6xl !px-2' : ''" />
<div :class="showSkeleton ? 'text-6xl' : ''" class="flex px-2">
{{ table.title }}
</div>
</div>
</GeneralTooltip>
<div>
<div
v-for="col in pkAndFkColumns"
:key="col.title"
class="w-full border-b-1 py-2 border-gray-100 keys"
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`"
>
<LazySmartsheetHeaderCell v-if="col" :column="col" :hide-menu="true" />
<div v-if="showSkeleton">
<Handle style="left: -20px" class="opacity-0" :position="Position.Left" type="target" :connectable="false" />
<Handle style="right: -15px" class="opacity-0" :position="Position.Right" type="source" :connectable="false" />
</div>
<div class="w-full mb-1"></div>
<div v-for="(col, index) in nonPkColumns" :key="col.title">
<div v-else-if="hasColumns">
<div
class="w-full h-full flex items-center min-w-32 border-gray-100 py-2 px-1"
:class="index + 1 === nonPkColumns.length ? 'rounded-b-lg' : 'border-b-1'"
v-for="col in data.pkAndFkColumns"
:key="col.title"
class="w-full h-full min-w-32 border-b-1 py-2 px-1 border-slate-200 bg-slate-100"
:class="`nc-erd-table-node-${table.table_name}-column-${col.column_name}`"
>
<LazySmartsheetHeaderCell v-if="col" :column="col" :hide-menu="true" />
</div>
<div v-for="(col, index) in data.nonPkColumns" :key="col.title">
<div
v-if="col.uidt === UITypes.LinkToAnotherRecord"
class="flex relative w-full"
:class="`nc-erd-table-node-${data.table_name}-column-${col.title?.toLowerCase().replace(' ', '_')}`"
class="relative w-full h-full flex items-center min-w-32 border-slate-200 py-2 px-1"
:class="index + 1 === data.nonPkColumns.length ? 'rounded-b-lg' : 'border-b-1'"
>
<Handle
:id="`s-${relatedColumnId(col)}-${data.id}`"
class="-right-4 opacity-0"
type="source"
:position="Position.Right"
<div
v-if="col.uidt === UITypes.LinkToAnotherRecord"
class="flex w-full"
:class="`nc-erd-table-node-${table.table_name}-column-${col.title?.toLowerCase().replace(' ', '_')}`"
>
<Handle
:id="`s-${relatedColumnId(col.colOptions)}-${table.id}`"
class="opacity-0 !right-[-1px]"
type="source"
:position="Position.Right"
:connectable="false"
/>
<Handle
:id="`d-${relatedColumnId(col.colOptions)}-${table.id}`"
class="opacity-0 !left-[-1px]"
type="target"
:position="Position.Left"
:connectable="false"
/>
<LazySmartsheetHeaderVirtualCell :column="col" :hide-menu="true" />
</div>
<LazySmartsheetHeaderVirtualCell
v-else-if="isVirtualCol(col)"
:column="col"
:hide-menu="true"
:class="`nc-erd-table-node-${table.table_name}-column-${col.column_name}`"
/>
<Handle
:id="`d-${relatedColumnId(col)}-${data.id}`"
class="-left-1 opacity-0"
type="target"
:position="Position.Left"
<LazySmartsheetHeaderCell
v-else
:column="col"
:hide-menu="true"
:class="`nc-erd-table-node-${table.table_name}-column-${col.column_name}`"
/>
<LazySmartsheetHeaderVirtualCell :column="col" :hide-menu="true" />
</div>
<LazySmartsheetHeaderVirtualCell
v-else-if="isVirtualCol(col)"
:column="col"
:hide-menu="true"
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`"
/>
<LazySmartsheetHeaderCell
v-else
:column="col"
:hide-menu="true"
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`"
/>
</div>
</div>
</div>
</div>
</GeneralTooltip>
</template>
<style scoped lang="scss">
.keys {
background-color: #f6f6f6;
}
</style>

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

@ -1,12 +1,11 @@
<script setup lang="ts">
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { ref, useGlobal, useMetas, useProject, watch } from '#imports'
import type { ERDConfig } from './utils'
import { reactive, ref, useMetas, useProject, watch } from '#imports'
const { table } = defineProps<{ table?: TableType }>()
const { includeM2M } = useGlobal()
const { tables: projectTables } = useProject()
const { metas, getMeta } = useMetas()
@ -14,9 +13,8 @@ const { metas, getMeta } = useMetas()
const tables = ref<TableType[]>([])
let isLoading = $ref(true)
const showAdvancedOptions = ref(false)
const config = ref({
const config = reactive<ERDConfig>({
showPkAndFk: true,
showViews: false,
showAllColumns: true,
@ -36,7 +34,8 @@ const loadMetaOfTablesNotInMetas = async (localTables: TableType[]) => {
}
const populateTables = async () => {
let localTables: TableType[] = []
let localTables: TableType[]
if (table) {
// if table is provided only get the table and its related tables
localTables = projectTables.value.filter(
@ -57,110 +56,47 @@ const populateTables = async () => {
tables.value = localTables
.filter(
(t) =>
// todo: table type is missing mm property in type definition
config.value.showMMTables ||
(!config.value.showMMTables && !t.mm) ||
config.showMMTables ||
(!config.showMMTables && !t.mm) ||
// Show mm table if it's the selected table
t.id === table?.id,
)
.filter((t) => (!config.value.showViews && t.type !== 'view') || config.value.showViews)
.filter((t) => config.singleTableMode || (!config.showViews && t.type !== 'view') || config.showViews)
isLoading = false
}
watch(
[config, metas],
async () => {
await populateTables()
},
{
deep: true,
},
)
watch([metas, projectTables], populateTables, {
flush: 'post',
immediate: true,
})
watch(
[projectTables],
async () => {
await populateTables()
},
{ immediate: true },
)
watch(config, populateTables, {
flush: 'post',
deep: true,
})
watch(
() => config.value.showAllColumns,
() => config.showAllColumns,
() => {
config.value.showPkAndFk = config.value.showAllColumns
config.showPkAndFk = config.showAllColumns
},
)
</script>
<template>
<div
class="w-full"
style="height: inherit"
:class="{
'nc-erd-vue-flow': !config.singleTableMode,
'nc-erd-vue-flow-single-table': config.singleTableMode,
}"
>
<div v-if="isLoading" class="h-full w-full flex flex-col justify-center items-center">
<div class="flex flex-row justify-center">
<a-spin size="large" />
</div>
</div>
<div v-else class="relative h-full">
<LazyErdFlow :tables="tables" :config="config" />
<div
class="absolute top-3 right-12 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"
>
<div class="flex flex-row items-center">
<a-checkbox
v-model:checked="config.showAllColumns"
v-e="['c:erd:showAllColumns']"
class="nc-erd-showColumns-checkbox"
/>
<span
class="ml-2 select-none nc-erd-showColumns-label"
style="font-size: 0.65rem"
@dblclick="showAdvancedOptions = true"
>
{{ $t('activity.erd.showColumns') }}
</span>
</div>
<div class="flex flex-row items-center">
<a-checkbox
v-model:checked="config.showPkAndFk"
v-e="['c:erd:showPkAndFk']"
class="nc-erd-showPkAndFk-checkbox"
:class="{
'nc-erd-showPkAndFk-checkbox-enabled': config.showAllColumns,
'nc-erd-showPkAndFk-checkbox-disabled': !config.showAllColumns,
'nc-erd-showPkAndFk-checkbox-checked': config.showPkAndFk,
'nc-erd-showPkAndFk-checkbox-unchecked': !config.showPkAndFk,
}"
:disabled="!config.showAllColumns"
/>
<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 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 text-[0.65rem]">{{ $t('activity.erd.showMMTables') }}</span>
</div>
<div v-if="showAdvancedOptions && includeM2M" class="flex flex-row items-center">
<a-checkbox
v-model:checked="config.showJunctionTableNames"
v-e="['c:erd:showJunctionTableNames']"
class="nc-erd-showJunctionTableNames-checkbox"
/>
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showJunctionTableNames') }}</span>
</div>
</div>
<div class="w-full" style="height: inherit" :class="[`nc-erd-vue-flow${config.singleTableMode ? '-single-table' : ''}`]">
<div class="relative h-full">
<LazyErdFlow :tables="tables" :config="config">
<GeneralOverlay v-model="isLoading" inline class="bg-gray-300/50">
<div class="h-full w-full flex flex-col justify-center items-center">
<a-spin size="large" />
</div>
</GeneralOverlay>
<ErdConfigPanel :config="config" />
<ErdHistogramPanel v-if="!config.singleTableMode" />
</LazyErdFlow>
</div>
</div>
</template>

225
packages/nc-gui/components/erd/utils.ts

@ -1,17 +1,38 @@
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import dagre from 'dagre'
import type { Edge, Elements } from '@vue-flow/core'
import type { Edge, EdgeMarker, Elements, Node } from '@vue-flow/core'
import type { MaybeRef } from '@vueuse/core'
import { Position, isEdge, isNode } from '@vue-flow/core'
import { computed, ref, unref, useMetas } from '#imports'
import { MarkerType, Position, isEdge, isNode } from '@vue-flow/core'
import { scaleLinear as d3ScaleLinear } from 'd3-scale'
import tinycolor from 'tinycolor2'
import { computed, ref, unref, useMetas, useTheme } from '#imports'
export interface ErdFlowConfig {
export interface ERDConfig {
showPkAndFk: boolean
showViews: boolean
showAllColumns: boolean
singleTableMode: boolean
showJunctionTableNames: boolean
showMMTables: boolean
}
export interface NodeData {
table: TableType
pkAndFkColumns: ColumnType[]
nonPkColumns: ColumnType[]
showPkAndFk: boolean
showAllColumns: boolean
color: string
columnLength: number
}
export interface EdgeData {
isManyToMany: boolean
isSelfRelation: boolean
label?: string
simpleLabel?: string
color: string
}
interface Relation {
@ -23,8 +44,18 @@ interface Relation {
type: 'mm' | 'hm'
}
export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<ErdFlowConfig>) {
const elements = ref<Elements>([])
/**
* This util is used to generate the ERD graph elements and layout them
*
* @param tables
* @param props
*/
export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<ERDConfig>) {
const elements = ref<Elements<NodeData | EdgeData>>([])
const { theme } = useTheme()
const colorScale = d3ScaleLinear<string>().domain([0, 2]).range([theme.value.primaryColor, theme.value.accentColor])
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
@ -35,6 +66,9 @@ export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<Er
const erdTables = computed(() => unref(tables))
const config = $computed(() => unref(props))
const nodeWidth = 300
const nodeHeight = $computed(() => (config.showViews && config.showAllColumns ? 50 : 40))
const relations = computed(() =>
erdTables.value.reduce((acc, table) => {
const meta = metasWithIdAsKey.value[table.id!]
@ -87,47 +121,88 @@ export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<Er
}, [] as Relation[]),
)
const edgeMMTableLabel = (modelId?: string) => {
if (!modelId) return ''
function edgeLabel({ type, source, target, modelId, childColId, parentColId }: Relation) {
const typeLabel = type === 'mm' ? 'many to many' : 'has many'
const parentCol = metasWithIdAsKey.value[source].columns?.find((col) => {
const colOptions = col.colOptions as LinkToAnotherRecordType
if (!colOptions) return false
return (
colOptions.fk_child_column_id === childColId &&
colOptions.fk_parent_column_id === parentColId &&
colOptions.fk_mm_model_id === modelId
)
})
const childCol = metasWithIdAsKey.value[target].columns?.find((col) => {
const colOptions = col.colOptions as LinkToAnotherRecordType
if (!colOptions) return false
return colOptions.fk_parent_column_id === (type === 'mm' ? childColId : parentColId)
})
if (!parentCol || !childCol) return ''
if (type === 'mm') {
if (config.showJunctionTableNames) {
if (!modelId) return ''
const mmModel = metasWithIdAsKey.value[modelId]
const mmModel = metasWithIdAsKey.value[modelId]
if (!mmModel) return ''
if (mmModel.title !== mmModel.table_name) {
return `${mmModel.title} (${mmModel.table_name})`
if (mmModel.title !== mmModel.table_name) {
return [`${mmModel.title} (${mmModel.table_name})`]
}
return [mmModel.title]
}
}
return mmModel.title
return [
// detailed edge label
`[${metasWithIdAsKey.value[source].title}] ${parentCol.title} - ${typeLabel} - ${childCol.title} [${metasWithIdAsKey.value[target].title}]`,
// simple edge label (for skeleton)
`${metasWithIdAsKey.value[source].title} - ${typeLabel} - ${metasWithIdAsKey.value[target].title}`,
]
}
function createNodes() {
return erdTables.value.flatMap((table) => {
if (!table.id) return []
return erdTables.value.reduce<Node<NodeData>[]>((acc, table) => {
if (!table.id) return acc
const columns =
metasWithIdAsKey.value[table.id].columns?.filter(
(col) => config.showAllColumns || (!config.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord),
) || []
return [
{
id: table.id,
data: {
...metasWithIdAsKey.value[table.id],
showPkAndFk: config.showPkAndFk,
showAllColumns: config.showAllColumns,
columnLength: columns.length,
},
type: 'custom',
position: { x: 0, y: 0 },
const pkAndFkColumns = columns.filter(() => config.showPkAndFk).filter((col) => col.pk || col.uidt === UITypes.ForeignKey)
const nonPkColumns = columns.filter((col) => !col.pk && col.uidt !== UITypes.ForeignKey)
acc.push({
id: table.id,
data: {
table: metasWithIdAsKey.value[table.id],
pkAndFkColumns,
nonPkColumns,
showPkAndFk: config.showPkAndFk,
showAllColumns: config.showAllColumns,
columnLength: columns.length,
color: '',
},
]
})
type: 'custom',
position: { x: 0, y: 0 },
})
return acc
}, [])
}
function createEdges() {
return relations.value.reduce<Edge[]>((acc, { source, target, childColId, parentColId, type, modelId }) => {
return relations.value.reduce<Edge<EdgeData>[]>((acc, { source, target, childColId, parentColId, type, modelId }) => {
let sourceColumnId, targetColumnId
let edgeLabel = ''
if (type === 'hm') {
sourceColumnId = childColId
@ -137,22 +212,34 @@ export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<Er
if (type === 'mm') {
sourceColumnId = parentColId
targetColumnId = childColId
edgeLabel = config.showJunctionTableNames ? edgeMMTableLabel(modelId) : ''
}
if (source !== target) dagreGraph.setEdge(source, target)
const [label, simpleLabel] = edgeLabel({
source,
target,
type,
childColId,
parentColId,
modelId,
})
acc.push({
id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}-#${edgeLabel}`,
id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}-#${label}`,
source: `${source}`,
target: `${target}`,
sourceHandle: `s-${sourceColumnId}-${source}`,
targetHandle: `d-${targetColumnId}-${target}`,
type: 'custom',
markerEnd: {
id: 'arrow-colored',
type: MarkerType.ArrowClosed,
},
data: {
isManyToMany: type === 'mm',
isSelfRelation: source === target && sourceColumnId === targetColumnId,
label: edgeLabel,
label,
simpleLabel,
color: '',
},
})
@ -160,46 +247,25 @@ export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<Er
}, [])
}
function connectNonConnectedNodes() {
const connectedNodes = new Set<string>()
elements.value.forEach((el) => {
if (isEdge(el)) {
connectedNodes.add(el.source)
connectedNodes.add(el.target)
}
})
const nonConnectedNodes = erdTables.value.filter((table) => !connectedNodes.has(table.id!))
if (nonConnectedNodes.length === 0) return
if (nonConnectedNodes.length === 1) {
const firstTable = erdTables.value.find((table) => table.type === 'table' && table.id !== nonConnectedNodes[0].id)
if (!firstTable) return
dagreGraph.setEdge(nonConnectedNodes[0].id!, firstTable.id!)
return
}
const firstNode = nonConnectedNodes[0]
nonConnectedNodes.forEach((node, index) => {
if (index === 0) return
const source = firstNode.id!
const target = node.id!
const boxShadow = (skeleton: boolean, color: string) => ({
border: 'none !important',
boxShadow: `0 0 0 ${skeleton ? '12' : '2'}px ${color}`,
})
dagreGraph.setEdge(source, target)
})
}
const layout = (skeleton = false) => {
elements.value = [...createNodes(), ...createEdges()] as Elements<NodeData | EdgeData>
const layout = () => {
elements.value = [...createNodes(), ...createEdges()]
if (!config.singleTableMode) connectNonConnectedNodes()
elements.value.forEach((el) => {
if (isNode(el)) {
dagreGraph.setNode(el.id, { width: 250, height: 50 * el.data.columnLength })
const node = el as Node<NodeData>
const colLength = node.data!.columnLength
const width = skeleton ? nodeWidth * 3 : nodeWidth
const height = nodeHeight + (skeleton ? 250 : colLength > 0 ? nodeHeight * colLength : nodeHeight)
dagreGraph.setNode(el.id, {
width,
height,
})
} else if (isEdge(el)) {
dagreGraph.setEdge(el.source, el.target)
}
@ -209,10 +275,31 @@ export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<Er
elements.value.forEach((el) => {
if (isNode(el)) {
const color = colorScale(dagreGraph.predecessors(el.id)!.length)
const nodeWithPosition = dagreGraph.node(el.id)
el.targetPosition = Position.Left
el.sourcePosition = Position.Right
el.position = { x: nodeWithPosition.x, y: nodeWithPosition.y }
el.class = ['rounded-lg'].join(' ')
el.data.color = color
el.style = (n) => {
if (n.selected) {
return boxShadow(skeleton, color)
}
return boxShadow(skeleton, '#64748B')
}
} else if (isEdge(el)) {
const node = elements.value.find((nodes) => nodes.id === el.source)
if (node) {
const color = node.data!.color
el.data.color = color
;(el.markerEnd as EdgeMarker).color = `#${tinycolor(color).toHex()}`
}
}
})
}

16
packages/nc-gui/components/general/Tooltip.vue

@ -1,19 +1,25 @@
<script lang="ts" setup>
import { onKeyStroke } from '@vueuse/core'
import type { CSSProperties } from '@vue/runtime-dom'
import { ref, useElementHover, watch } from '#imports'
import { controlledRef, ref, useElementHover, watch } from '#imports'
interface Props {
// Key to be pressed on hover to trigger the tooltip
modifierKey?: string
tooltipStyle?: CSSProperties
// force disable tooltip
disabled?: boolean
}
const { modifierKey, tooltipStyle } = defineProps<Props>()
const { modifierKey, tooltipStyle, disabled } = defineProps<Props>()
const el = ref()
const showTooltip = ref(false)
const showTooltip = controlledRef(false, {
onBeforeChange: (shouldShow) => {
if (shouldShow && disabled) return false
},
})
const isHovering = useElementHover(() => el.value)
@ -44,8 +50,8 @@ onKeyStroke(
{ eventName: 'keyup' },
)
watch([isHovering, () => modifierKey], ([hovering, key]) => {
if (!hovering) {
watch([isHovering, () => modifierKey, () => disabled], ([hovering, key, isDisabled]) => {
if (!hovering || isDisabled) {
showTooltip.value = false
return
}

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

@ -1,14 +1,14 @@
<script lang="ts" setup>
interface Props {
modelValue: boolean
}
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)
@ -23,21 +23,31 @@ const selectedView = inject(ActiveViewInj)
:closable="false"
wrap-class-name="erd-single-table-modal"
transition-name="fade"
:destroy-on-close="true"
>
<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">
<div class="flex justify-between w-full items-start px-[24px] pt-6 pb-4 border-b-1">
<div class="select-none text-slate-500 font-semibold">
{{ `${$t('title.erdView')}: ${selectedView?.title}` }}
</a-typography-title>
</div>
<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 class="flex h-full items-center justify-center rounded group" @click="vModel = false">
<MdiClose class="cursor-pointer mt-1 nc-modal-close group-hover:text-accent text-opacity-100" />
</div>
</div>
<div class="w-full h-full !py-0 !px-2" style="height: 70vh">
<div class="w-full h-70vh">
<LazyErdView :table="meta" />
</div>
</a-modal>
</template>
<style>
.erd-single-table-modal {
.ant-modal {
@apply !top-[50px];
}
.ant-modal-body {
@apply !p-0;
}
}
</style>

644
packages/nc-gui/package-lock.json generated

@ -8,12 +8,13 @@
"dependencies": {
"@ckpack/vue-color": "^1.2.0",
"@vue-flow/additional-components": "^1.1.0",
"@vue-flow/core": "^1.1.1",
"@vue-flow/core": "^1.1.3",
"@vuelidate/core": "^2.0.0-alpha.44",
"@vuelidate/validators": "^2.0.0-alpha.31",
"@vueuse/core": "^9.0.2",
"@vueuse/integrations": "^9.0.2",
"ant-design-vue": "^3.2.11",
"d3-scale": "^4.0.2",
"dagre": "^0.8.5",
"dayjs": "^1.11.3",
"file-saver": "^2.0.5",
@ -56,6 +57,7 @@
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0",
"@types/d3": "^7.4.0",
"@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5",
"@types/papaparse": "^5.3.2",
@ -2904,6 +2906,259 @@
"@types/node": "*"
}
},
"node_modules/@types/d3": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz",
"integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==",
"dev": true,
"dependencies": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
"integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==",
"dev": true
},
"node_modules/@types/d3-axis": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz",
"integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==",
"dev": true,
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-brush": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz",
"integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==",
"dev": true,
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-chord": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz",
"integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==",
"dev": true
},
"node_modules/@types/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==",
"dev": true
},
"node_modules/@types/d3-contour": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz",
"integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==",
"dev": true,
"dependencies": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"node_modules/@types/d3-delaunay": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
"integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==",
"dev": true
},
"node_modules/@types/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==",
"dev": true
},
"node_modules/@types/d3-drag": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz",
"integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==",
"dev": true,
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-dsv": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz",
"integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==",
"dev": true
},
"node_modules/@types/d3-ease": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
"integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==",
"dev": true
},
"node_modules/@types/d3-fetch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz",
"integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==",
"dev": true,
"dependencies": {
"@types/d3-dsv": "*"
}
},
"node_modules/@types/d3-force": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz",
"integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==",
"dev": true
},
"node_modules/@types/d3-format": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
"integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==",
"dev": true
},
"node_modules/@types/d3-geo": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz",
"integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==",
"dev": true,
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-hierarchy": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz",
"integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==",
"dev": true
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
"dev": true,
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
"integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==",
"dev": true
},
"node_modules/@types/d3-polygon": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz",
"integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==",
"dev": true
},
"node_modules/@types/d3-quadtree": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz",
"integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==",
"dev": true
},
"node_modules/@types/d3-random": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz",
"integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==",
"dev": true
},
"node_modules/@types/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
"dev": true,
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-scale-chromatic": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz",
"integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==",
"dev": true
},
"node_modules/@types/d3-selection": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz",
"integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==",
"dev": true
},
"node_modules/@types/d3-shape": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz",
"integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==",
"dev": true,
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
"dev": true
},
"node_modules/@types/d3-time-format": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz",
"integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==",
"dev": true
},
"node_modules/@types/d3-timer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
"integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==",
"dev": true
},
"node_modules/@types/d3-transition": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.2.tgz",
"integrity": "sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==",
"dev": true,
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz",
"integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==",
"dev": true,
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/dagre": {
"version": "0.7.48",
"resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.48.tgz",
@ -2955,6 +3210,12 @@
"@types/node": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.10",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -3289,9 +3550,9 @@
}
},
"node_modules/@vue-flow/core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.1.1.tgz",
"integrity": "sha512-zXEmZl9Rxrpi9+EzQoa//u0+pCzStwlISGdWw/WjXvVrBBfg6goW20k66TjHoJbzK3yNRN5b4lBBR0lVWZh/0w==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.1.3.tgz",
"integrity": "sha512-MuJjWLexkZ5RiMY/LmuyRZXiXKo8ttaKSPk02RYP8SoWVj6Kr0XglWh6FJdQE0bQhqpwsXBka+4EQrI4B/ueSw==",
"dependencies": {
"@vueuse/core": "^9.3.0",
"d3-drag": "^3.0.0",
@ -5824,6 +6085,17 @@
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==",
"dev": true
},
"node_modules/d3-array": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz",
"integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
@ -5860,6 +6132,14 @@
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
@ -5871,6 +6151,21 @@
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
@ -5879,6 +6174,28 @@
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
@ -9446,6 +9763,14 @@
"node": ">= 0.4"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"engines": {
"node": ">=12"
}
},
"node_modules/ioredis": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.2.3.tgz",
@ -18684,6 +19009,259 @@
"@types/node": "*"
}
},
"@types/d3": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz",
"integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==",
"dev": true,
"requires": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"@types/d3-array": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
"integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==",
"dev": true
},
"@types/d3-axis": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz",
"integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==",
"dev": true,
"requires": {
"@types/d3-selection": "*"
}
},
"@types/d3-brush": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz",
"integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==",
"dev": true,
"requires": {
"@types/d3-selection": "*"
}
},
"@types/d3-chord": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz",
"integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==",
"dev": true
},
"@types/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==",
"dev": true
},
"@types/d3-contour": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz",
"integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==",
"dev": true,
"requires": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"@types/d3-delaunay": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
"integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==",
"dev": true
},
"@types/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==",
"dev": true
},
"@types/d3-drag": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz",
"integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==",
"dev": true,
"requires": {
"@types/d3-selection": "*"
}
},
"@types/d3-dsv": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz",
"integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==",
"dev": true
},
"@types/d3-ease": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
"integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==",
"dev": true
},
"@types/d3-fetch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz",
"integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==",
"dev": true,
"requires": {
"@types/d3-dsv": "*"
}
},
"@types/d3-force": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz",
"integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==",
"dev": true
},
"@types/d3-format": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
"integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==",
"dev": true
},
"@types/d3-geo": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz",
"integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==",
"dev": true,
"requires": {
"@types/geojson": "*"
}
},
"@types/d3-hierarchy": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz",
"integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==",
"dev": true
},
"@types/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
"dev": true,
"requires": {
"@types/d3-color": "*"
}
},
"@types/d3-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
"integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==",
"dev": true
},
"@types/d3-polygon": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz",
"integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==",
"dev": true
},
"@types/d3-quadtree": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz",
"integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==",
"dev": true
},
"@types/d3-random": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz",
"integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==",
"dev": true
},
"@types/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
"dev": true,
"requires": {
"@types/d3-time": "*"
}
},
"@types/d3-scale-chromatic": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz",
"integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==",
"dev": true
},
"@types/d3-selection": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz",
"integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==",
"dev": true
},
"@types/d3-shape": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz",
"integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==",
"dev": true,
"requires": {
"@types/d3-path": "*"
}
},
"@types/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
"dev": true
},
"@types/d3-time-format": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz",
"integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==",
"dev": true
},
"@types/d3-timer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
"integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==",
"dev": true
},
"@types/d3-transition": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.2.tgz",
"integrity": "sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==",
"dev": true,
"requires": {
"@types/d3-selection": "*"
}
},
"@types/d3-zoom": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz",
"integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==",
"dev": true,
"requires": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"@types/dagre": {
"version": "0.7.48",
"resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.48.tgz",
@ -18735,6 +19313,12 @@
"@types/node": "*"
}
},
"@types/geojson": {
"version": "7946.0.10",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -18967,9 +19551,9 @@
"requires": {}
},
"@vue-flow/core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.1.1.tgz",
"integrity": "sha512-zXEmZl9Rxrpi9+EzQoa//u0+pCzStwlISGdWw/WjXvVrBBfg6goW20k66TjHoJbzK3yNRN5b4lBBR0lVWZh/0w==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.1.3.tgz",
"integrity": "sha512-MuJjWLexkZ5RiMY/LmuyRZXiXKo8ttaKSPk02RYP8SoWVj6Kr0XglWh6FJdQE0bQhqpwsXBka+4EQrI4B/ueSw==",
"requires": {
"@vueuse/core": "^9.3.0",
"d3-drag": "^3.0.0",
@ -20839,6 +21423,14 @@
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==",
"dev": true
},
"d3-array": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz",
"integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==",
"requires": {
"internmap": "1 - 2"
}
},
"d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
@ -20863,6 +21455,11 @@
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="
},
"d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="
},
"d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
@ -20871,11 +21468,39 @@
"d3-color": "1 - 3"
}
},
"d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"requires": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
}
},
"d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="
},
"d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==",
"requires": {
"d3-array": "2 - 3"
}
},
"d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"requires": {
"d3-time": "1 - 3"
}
},
"d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
@ -23445,6 +24070,11 @@
"side-channel": "^1.0.4"
}
},
"internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="
},
"ioredis": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.2.3.tgz",

4
packages/nc-gui/package.json

@ -17,12 +17,13 @@
"dependencies": {
"@ckpack/vue-color": "^1.2.0",
"@vue-flow/additional-components": "^1.1.0",
"@vue-flow/core": "^1.1.1",
"@vue-flow/core": "^1.1.3",
"@vuelidate/core": "^2.0.0-alpha.44",
"@vuelidate/validators": "^2.0.0-alpha.31",
"@vueuse/core": "^9.0.2",
"@vueuse/integrations": "^9.0.2",
"ant-design-vue": "^3.2.11",
"d3-scale": "^4.0.2",
"dagre": "^0.8.5",
"dayjs": "^1.11.3",
"file-saver": "^2.0.5",
@ -65,6 +66,7 @@
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@nuxt/image-edge": "^1.0.0-27657146.da85542",
"@types/axios": "^0.14.0",
"@types/d3": "^7.4.0",
"@types/dagre": "^0.7.48",
"@types/file-saver": "^2.0.5",
"@types/papaparse": "^5.3.2",

Loading…
Cancel
Save