mirror of https://github.com/nocodb/nocodb
github-actions[bot]
2 years ago
committed by
GitHub
377 changed files with 62934 additions and 41056 deletions
@ -0,0 +1,24 @@
|
||||
spec: |
||||
name: nocodb |
||||
services: |
||||
- name: nocodb |
||||
image: |
||||
registry_type: DOCKER_HUB |
||||
registry: nocodb |
||||
repository: nocodb |
||||
tag: latest |
||||
run_command: "./server/scripts/digitalocean-postbuild.sh" |
||||
instance_size_slug: "basic-s" |
||||
health_check: |
||||
initial_delay_seconds: 10 |
||||
http_path: /api/health |
||||
envs: |
||||
- key: NODE_ENV |
||||
value: "production" |
||||
- key: DATABASE_URL |
||||
scope: RUN_TIME |
||||
value: ${postgres.DATABASE_URL} |
||||
databases: |
||||
- name: postgres |
||||
engine: PG |
||||
production: false |
@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager"> |
||||
<configuration default="false" name="Drop metadb" type="NodeJSConfigurationType" path-to-js-file="$PROJECT_DIR$/packages/nocodb/src/run/deleteMetaDb.js" working-dir="$PROJECT_DIR$/packages/nocodb/src/run"> |
||||
<configuration default="false" name="Drop metadb" type="NodeJSConfigurationType" path-to-js-file="deleteMetaDb.js" working-dir="$PROJECT_DIR$/packages/nocodb/src/run"> |
||||
<method v="2" /> |
||||
</configuration> |
||||
</component> |
@ -1,12 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager"> |
||||
<configuration default="false" name="Run GUI" type="js.build_tools.npm"> |
||||
<package-json value="$PROJECT_DIR$/packages/nc-gui/package.json" /> |
||||
<command value="run" /> |
||||
<scripts> |
||||
<script value="dev" /> |
||||
</scripts> |
||||
<node-interpreter value="project" /> |
||||
<envs /> |
||||
<method v="2" /> |
||||
</configuration> |
||||
</component> |
@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager"> |
||||
<configuration default="false" name="Start::IDE" type="CompoundRunConfigurationType"> |
||||
<toRun name="Run::Backend" type="js.build_tools.npm" /> |
||||
<toRun name="Run::Frontend" type="js.build_tools.npm" /> |
||||
<method v="2" /> |
||||
</configuration> |
||||
</component> |
@ -0,0 +1,5 @@
|
||||
<template> |
||||
<div class="w-full h-full !py-0 !px-2" style="height: 70vh"> |
||||
<ErdView /> |
||||
</div> |
||||
</template> |
@ -0,0 +1,227 @@
|
||||
<script setup lang="ts"> |
||||
import type { Edge, Node } 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' |
||||
import TableNode from './TableNode.vue' |
||||
import RelationEdge from './RelationEdge.vue' |
||||
|
||||
interface Props { |
||||
tables: any[] |
||||
config: { |
||||
showPkAndFk: boolean |
||||
showViews: boolean |
||||
showAllColumns: boolean |
||||
singleTableMode: boolean |
||||
showJunctionTableNames: boolean |
||||
} |
||||
} |
||||
|
||||
const { tables, config } = defineProps<Props>() |
||||
|
||||
const { metasWithIdAsKey } = useMetas() |
||||
|
||||
const { $destroy, fitView } = useVueFlow() |
||||
|
||||
const nodes = ref<Node[]>([]) |
||||
const edges = ref<Edge[]>([]) |
||||
|
||||
let dagreGraph: dagre.graphlib.Graph |
||||
|
||||
const initDagre = () => { |
||||
dagreGraph = new dagre.graphlib.Graph() |
||||
dagreGraph.setDefaultEdgeLabel(() => ({})) |
||||
dagreGraph.setGraph({ rankdir: 'LR' }) |
||||
} |
||||
|
||||
const populateInitialNodes = () => { |
||||
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), |
||||
) || [] |
||||
|
||||
dagreGraph.setNode(table.id, { width: 250, height: 50 * columns.length }) |
||||
|
||||
return [ |
||||
{ |
||||
id: table.id, |
||||
data: { ...metasWithIdAsKey.value[table.id], showPkAndFk: config.showPkAndFk, showAllColumns: config.showAllColumns }, |
||||
type: 'custom', |
||||
position: { x: 0, y: 0 }, |
||||
}, |
||||
] |
||||
}) |
||||
} |
||||
|
||||
const populateEdges = () => { |
||||
const ltarColumns = tables.reduce<ColumnType[]>((acc, table) => { |
||||
const meta = metasWithIdAsKey.value[table.id!] |
||||
const columns = meta.columns?.filter( |
||||
(column: ColumnType) => column.uidt === UITypes.LinkToAnotherRecord && column.system !== 1, |
||||
) |
||||
|
||||
columns?.forEach((column: ColumnType) => { |
||||
if ((column.colOptions as LinkToAnotherRecordType)?.type === 'hm') { |
||||
acc.push(column) |
||||
} |
||||
|
||||
if ((column.colOptions as LinkToAnotherRecordType).type === 'mm') { |
||||
// Avoid duplicate mm connections |
||||
const correspondingColumn = acc.find( |
||||
(c) => |
||||
(c.colOptions as LinkToAnotherRecordType | FormulaType | RollupType | LookupType).type === 'mm' && |
||||
(c.colOptions as LinkToAnotherRecordType).fk_parent_column_id === |
||||
(column.colOptions as LinkToAnotherRecordType).fk_child_column_id && |
||||
(c.colOptions as LinkToAnotherRecordType).fk_child_column_id === |
||||
(column.colOptions as LinkToAnotherRecordType).fk_parent_column_id, |
||||
) |
||||
if (!correspondingColumn) { |
||||
acc.push(column) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
return acc |
||||
}, [] as ColumnType[]) |
||||
|
||||
const edgeMMTableLabel = (modelId: string) => { |
||||
const mmModel = metasWithIdAsKey.value[modelId] |
||||
if (mmModel.title !== mmModel.table_name) { |
||||
return `${mmModel.title} (${mmModel.table_name})` |
||||
} |
||||
return mmModel.title |
||||
} |
||||
|
||||
edges.value = ltarColumns.map((column) => { |
||||
const source = column.fk_model_id! |
||||
const target = (column.colOptions as LinkToAnotherRecordType).fk_related_model_id! |
||||
|
||||
let sourceColumnId, targetColumnId |
||||
let edgeLabel = '' |
||||
|
||||
if ((column.colOptions as LinkToAnotherRecordType).type === 'hm') { |
||||
sourceColumnId = (column.colOptions as LinkToAnotherRecordType).fk_child_column_id |
||||
targetColumnId = (column.colOptions as LinkToAnotherRecordType).fk_child_column_id |
||||
} |
||||
|
||||
if ((column.colOptions as LinkToAnotherRecordType).type === 'mm') { |
||||
sourceColumnId = (column.colOptions as LinkToAnotherRecordType).fk_parent_column_id |
||||
targetColumnId = (column.colOptions as LinkToAnotherRecordType).fk_child_column_id |
||||
edgeLabel = config.showJunctionTableNames |
||||
? edgeMMTableLabel((column.colOptions as LinkToAnotherRecordType).fk_mm_model_id!) |
||||
: '' |
||||
} |
||||
|
||||
if (source !== target) dagreGraph.setEdge(source, target) |
||||
|
||||
return { |
||||
id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}-#${edgeLabel}`, |
||||
source: `${source}`, |
||||
target: `${target}`, |
||||
sourceHandle: `s-${sourceColumnId}-${source}`, |
||||
targetHandle: `d-${targetColumnId}-${target}`, |
||||
type: 'custom', |
||||
data: { |
||||
column, |
||||
isSelfRelation: source === target && sourceColumnId === targetColumnId, |
||||
label: edgeLabel, |
||||
}, |
||||
} |
||||
}) |
||||
} |
||||
|
||||
const connectNonConnectedNodes = () => { |
||||
const connectedNodes = new Set<string>() |
||||
|
||||
edges.value.forEach((edge) => { |
||||
connectedNodes.add(edge.source) |
||||
connectedNodes.add(edge.target) |
||||
}) |
||||
|
||||
const nonConnectedNodes = tables.filter((table) => !connectedNodes.has(table.id!)) |
||||
|
||||
if (nonConnectedNodes.length === 0) return |
||||
|
||||
if (nonConnectedNodes.length === 1) { |
||||
const firstTable = tables.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 |
||||
|
||||
dagreGraph.setEdge(source, target) |
||||
}) |
||||
} |
||||
|
||||
const layoutNodes = () => { |
||||
if (!config.singleTableMode) connectNonConnectedNodes() |
||||
|
||||
dagre.layout(dagreGraph) |
||||
|
||||
nodes.value = nodes.value.flatMap((node) => { |
||||
const nodeWithPosition = dagreGraph.node(node.id) |
||||
|
||||
if (!nodeWithPosition) return [] |
||||
|
||||
return [{ ...node, position: { x: nodeWithPosition.x, y: nodeWithPosition.y } } as Node] |
||||
}) |
||||
} |
||||
|
||||
const init = () => { |
||||
initDagre() |
||||
populateInitialNodes() |
||||
populateEdges() |
||||
layoutNodes() |
||||
|
||||
setTimeout(() => fitView({ duration: 300 })) |
||||
} |
||||
|
||||
init() |
||||
|
||||
onScopeDispose($destroy) |
||||
|
||||
watch([() => tables, () => config], init, { deep: true, flush: 'pre' }) |
||||
</script> |
||||
|
||||
<template> |
||||
<VueFlow :nodes="nodes" :edges="edges" 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"> |
||||
<MdiEyeCircleOutline class="text-primary" /> |
||||
<div>{{ $t('objects.sqlVIew') }}</div> |
||||
</div> |
||||
</div> |
||||
</VueFlow> |
||||
</template> |
@ -0,0 +1,161 @@
|
||||
<script setup> |
||||
import { EdgeText, getBezierPath, getEdgeCenter } from '@braks/vue-flow' |
||||
import { computed } from 'vue' |
||||
|
||||
const props = defineProps({ |
||||
id: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
sourceX: { |
||||
type: Number, |
||||
required: true, |
||||
}, |
||||
sourceY: { |
||||
type: Number, |
||||
required: true, |
||||
}, |
||||
targetX: { |
||||
type: Number, |
||||
required: true, |
||||
}, |
||||
targetY: { |
||||
type: Number, |
||||
required: true, |
||||
}, |
||||
sourcePosition: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
targetPosition: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
data: { |
||||
type: Object, |
||||
required: false, |
||||
}, |
||||
markerEnd: { |
||||
type: String, |
||||
required: false, |
||||
}, |
||||
style: { |
||||
type: Object, |
||||
required: false, |
||||
}, |
||||
sourceHandleId: { |
||||
type: String, |
||||
required: false, |
||||
}, |
||||
targetHandleId: { |
||||
type: String, |
||||
required: false, |
||||
}, |
||||
}) |
||||
|
||||
const data = toRef(props, 'data') |
||||
|
||||
const isManyToMany = computed(() => data.value.column?.colOptions?.type === 'mm') |
||||
|
||||
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 getBezierPath({ |
||||
sourceX: props.sourceX, |
||||
sourceY: props.sourceY, |
||||
sourcePosition: props.sourcePosition, |
||||
targetX: props.targetX, |
||||
targetY: props.targetY, |
||||
targetPosition: props.targetPosition, |
||||
}) |
||||
}) |
||||
|
||||
const center = computed(() => |
||||
getEdgeCenter({ |
||||
sourceX: props.sourceX, |
||||
sourceY: props.sourceY, |
||||
targetX: props.targetX, |
||||
targetY: props.targetY, |
||||
}), |
||||
) |
||||
</script> |
||||
|
||||
<script> |
||||
export default { |
||||
inheritAttrs: false, |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<path |
||||
:id="id" |
||||
:style="style" |
||||
class="path-wrapper p-4 hover:cursor-pointer" |
||||
:stroke-width="8" |
||||
fill="none" |
||||
:d="edgePath" |
||||
: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" |
||||
:marker-end="markerEnd" |
||||
/> |
||||
|
||||
<EdgeText |
||||
v-if="data.label?.length > 0" |
||||
:class="`nc-erd-table-label-${data.label.toLowerCase().replace(' ', '-').replace('\(', '').replace(')', '')}`" |
||||
:x="center[0]" |
||||
:y="center[1]" |
||||
: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="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> |
||||
|
||||
<style scoped lang="scss"> |
||||
.path-wrapper:hover + .path { |
||||
@apply stroke-green-500; |
||||
stroke-width: 2; |
||||
} |
||||
.path:hover { |
||||
stroke-width: 2; |
||||
} |
||||
</style> |
@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup> |
||||
import type { NodeProps } from '@braks/vue-flow' |
||||
import { Handle, Position } from '@braks/vue-flow' |
||||
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||
import { UITypes, isVirtualCol } from 'nocodb-sdk' |
||||
import type { Ref } from 'vue' |
||||
|
||||
interface Props extends NodeProps { |
||||
data: TableType & { showPkAndFk: boolean; showAllColumns: boolean } |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
|
||||
const { data } = toRefs(props) |
||||
|
||||
provide(MetaInj, data as Ref<TableType>) |
||||
|
||||
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 |
||||
</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 modifier-key="Alt"> |
||||
<template #title> {{ data.table_name }} </template> |
||||
<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" |
||||
> |
||||
<MdiTableLarge v-if="data.type === 'table'" class="text-primary" /> |
||||
<MdiEyeCircleOutline v-else class="text-primary" /> |
||||
<div class="flex pl-1.5"> |
||||
{{ data.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}`" |
||||
> |
||||
<SmartsheetHeaderCell v-if="col" :column="col" :hide-menu="true" /> |
||||
</div> |
||||
|
||||
<div class="w-full mb-1"></div> |
||||
|
||||
<div v-for="(col, index) in nonPkColumns" :key="col.title"> |
||||
<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'" |
||||
> |
||||
<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(' ', '_')}`" |
||||
> |
||||
<Handle |
||||
:id="`s-${relatedColumnId(col)}-${data.id}`" |
||||
class="-right-4 opacity-0" |
||||
type="source" |
||||
:position="Position.Right" |
||||
/> |
||||
|
||||
<Handle |
||||
:id="`d-${relatedColumnId(col)}-${data.id}`" |
||||
class="-left-1 opacity-0" |
||||
type="target" |
||||
:position="Position.Left" |
||||
/> |
||||
<SmartsheetHeaderVirtualCell :column="col" :hide-menu="true" /> |
||||
</div> |
||||
<SmartsheetHeaderVirtualCell |
||||
v-else-if="isVirtualCol(col)" |
||||
:column="col" |
||||
:hide-menu="true" |
||||
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`" |
||||
/> |
||||
|
||||
<SmartsheetHeaderCell |
||||
v-else |
||||
:column="col" |
||||
:hide-menu="true" |
||||
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.keys { |
||||
background-color: #f6f6f6; |
||||
} |
||||
</style> |
@ -0,0 +1,162 @@
|
||||
<script setup lang="ts"> |
||||
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk' |
||||
import { UITypes } from 'nocodb-sdk' |
||||
|
||||
const { table } = defineProps<{ table?: TableType }>() |
||||
|
||||
const { includeM2M } = useGlobal() |
||||
|
||||
const { tables: projectTables } = useProject() |
||||
const tables = ref<TableType[]>([]) |
||||
const { metas, getMeta } = useMetas() |
||||
|
||||
let isLoading = $ref(true) |
||||
const showAdvancedOptions = ref(false) |
||||
|
||||
const config = ref({ |
||||
showPkAndFk: true, |
||||
showViews: false, |
||||
showAllColumns: true, |
||||
singleTableMode: !!table, |
||||
showMMTables: false, |
||||
showJunctionTableNames: false, |
||||
}) |
||||
|
||||
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 |
||||
localTables = projectTables.value.filter( |
||||
(t) => |
||||
t.id === table.id || |
||||
table.columns?.find( |
||||
(column) => |
||||
column.uidt === UITypes.LinkToAnotherRecord && |
||||
(column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id === t.id, |
||||
), |
||||
) |
||||
} else { |
||||
localTables = projectTables.value |
||||
} |
||||
|
||||
await loadMetaOfTablesNotInMetas(localTables) |
||||
|
||||
tables.value = localTables |
||||
.filter( |
||||
(t) => |
||||
// todo: table type is missing mm property in type definition |
||||
config.value.showMMTables || |
||||
(!config.value.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) |
||||
|
||||
isLoading = false |
||||
} |
||||
|
||||
watch( |
||||
[config, metas], |
||||
async () => { |
||||
await populateTables() |
||||
}, |
||||
{ |
||||
deep: true, |
||||
}, |
||||
) |
||||
|
||||
watch( |
||||
[projectTables], |
||||
async () => { |
||||
await populateTables() |
||||
}, |
||||
{ immediate: true }, |
||||
) |
||||
|
||||
watch( |
||||
() => config.value.showAllColumns, |
||||
() => { |
||||
config.value.showPkAndFk = config.value.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"> |
||||
<ErdFlow :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" |
||||
> |
||||
<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> |
||||
</div> |
||||
</template> |
@ -0,0 +1,31 @@
|
||||
<script setup lang="ts"> |
||||
import { useSidebar } from '#imports' |
||||
|
||||
const rightSidebar = useSidebar('nc-right-sidebar') |
||||
const leftSidebar = useSidebar('nc-left-sidebar') |
||||
|
||||
const isSidebarsOpen = computed({ |
||||
get: () => rightSidebar.isOpen.value || leftSidebar.isOpen.value, |
||||
set: (value) => { |
||||
rightSidebar.toggle(value) |
||||
leftSidebar.toggle(value) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-tooltip placement="left"> |
||||
<!-- todo: i18n --> |
||||
<template #title> |
||||
<span class="text-xs">{{ isSidebarsOpen ? 'Full width' : 'Exit full width' }}</span> |
||||
</template> |
||||
<div |
||||
v-e="['c:toolbar:fullscreen']" |
||||
class="nc-fullscreen-btn cursor-pointer flex align-center self-center px-2 py-2 mr-2" |
||||
@click="isSidebarsOpen = !isSidebarsOpen" |
||||
> |
||||
<IcTwotoneWidthFull v-if="isSidebarsOpen" class="text-gray-300" /> |
||||
<IcTwotoneWidthNormal v-else class="text-gray-300" /> |
||||
</div> |
||||
</a-tooltip> |
||||
</template> |
@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup> |
||||
import { onKeyStroke } from '@vueuse/core' |
||||
|
||||
interface Props { |
||||
// Key to be pressed on hover to trigger the tooltip |
||||
modifierKey?: string |
||||
wrapperClass?: string |
||||
} |
||||
|
||||
const { modifierKey } = defineProps<Props>() |
||||
|
||||
const showTooltip = ref(false) |
||||
const isMouseOver = ref(false) |
||||
|
||||
if (modifierKey) { |
||||
onKeyStroke(modifierKey, (e) => { |
||||
e.preventDefault() |
||||
if (modifierKey && isMouseOver.value) { |
||||
showTooltip.value = true |
||||
} |
||||
}) |
||||
} |
||||
|
||||
watch(isMouseOver, (val) => { |
||||
if (!val) { |
||||
showTooltip.value = false |
||||
} |
||||
|
||||
// Show tooltip on mouseover if no modifier key is provided |
||||
if (val && !modifierKey) { |
||||
showTooltip.value = true |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-tooltip v-model:visible="showTooltip" :trigger="[]"> |
||||
<template #title> |
||||
<slot name="title" /> |
||||
</template> |
||||
<div class="w-full" :class="wrapperClass" @mouseenter="isMouseOver = true" @mouseleave="isMouseOver = false"> |
||||
<slot /> |
||||
</div> |
||||
</a-tooltip> |
||||
</template> |
@ -0,0 +1,42 @@
|
||||
<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> |
||||
<a-modal |
||||
v-model:visible="vModel" |
||||
size="small" |
||||
:footer="null" |
||||
width="max(900px,60vw)" |
||||
:closable="false" |
||||
wrap-class-name="erd-single-table-modal" |
||||
transition-name="fade" |
||||
> |
||||
<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> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue