|
|
@ -2,11 +2,23 @@ |
|
|
|
import type { TableType } from 'nocodb-sdk' |
|
|
|
import type { TableType } from 'nocodb-sdk' |
|
|
|
import Sortable from 'sortablejs' |
|
|
|
import Sortable from 'sortablejs' |
|
|
|
import { Empty } from 'ant-design-vue' |
|
|
|
import { Empty } from 'ant-design-vue' |
|
|
|
import { useNuxtApp } from '#app' |
|
|
|
import { |
|
|
|
import { computed, useProject, useTable, useTabs, useUIPermission, watchEffect } from '#imports' |
|
|
|
computed, |
|
|
|
|
|
|
|
inject, |
|
|
|
|
|
|
|
reactive, |
|
|
|
|
|
|
|
ref, |
|
|
|
|
|
|
|
useDialog, |
|
|
|
|
|
|
|
useNuxtApp, |
|
|
|
|
|
|
|
useProject, |
|
|
|
|
|
|
|
useTable, |
|
|
|
|
|
|
|
useTabs, |
|
|
|
|
|
|
|
useUIPermission, |
|
|
|
|
|
|
|
watchEffect, |
|
|
|
|
|
|
|
} from '#imports' |
|
|
|
import DlgAirtableImport from '~/components/dlg/AirtableImport.vue' |
|
|
|
import DlgAirtableImport from '~/components/dlg/AirtableImport.vue' |
|
|
|
import DlgQuickImport from '~/components/dlg/QuickImport.vue' |
|
|
|
import DlgQuickImport from '~/components/dlg/QuickImport.vue' |
|
|
|
import DlgTableCreate from '~/components/dlg/TableCreate.vue' |
|
|
|
import DlgTableCreate from '~/components/dlg/TableCreate.vue' |
|
|
|
|
|
|
|
import DlgTableRename from '~/components/dlg/TableRename.vue' |
|
|
|
import { TabType } from '~/composables' |
|
|
|
import { TabType } from '~/composables' |
|
|
|
import MdiView from '~icons/mdi/eye-circle-outline' |
|
|
|
import MdiView from '~icons/mdi/eye-circle-outline' |
|
|
|
import MdiTableLarge from '~icons/mdi/table-large' |
|
|
|
import MdiTableLarge from '~icons/mdi/table-large' |
|
|
@ -27,16 +39,25 @@ const { isUIAllowed } = useUIPermission() |
|
|
|
|
|
|
|
|
|
|
|
const isLocked = inject('TreeViewIsLockedInj') |
|
|
|
const isLocked = inject('TreeViewIsLockedInj') |
|
|
|
|
|
|
|
|
|
|
|
const tablesById = $computed<Record<string, TableType>>(() => |
|
|
|
let key = $ref(0) |
|
|
|
tables?.value?.reduce((acc: Record<string, TableType>, table: TableType) => { |
|
|
|
|
|
|
|
acc[table.id as string] = table |
|
|
|
const menuRef = $ref<HTMLLIElement>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const filterQuery = $ref('') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const activeTable = computed(() => ([TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.title : null)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tablesById = $computed(() => |
|
|
|
|
|
|
|
tables.value?.reduce((acc: Record<string, TableType>, table) => { |
|
|
|
|
|
|
|
acc[table.id!] = table |
|
|
|
|
|
|
|
|
|
|
|
return acc |
|
|
|
return acc |
|
|
|
}, {}), |
|
|
|
}, {}), |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
let key = $ref(0) |
|
|
|
const filteredTables = $computed(() => |
|
|
|
|
|
|
|
tables.value?.filter((table) => !filterQuery || table.title.toLowerCase().includes(filterQuery.toLowerCase())), |
|
|
|
const menuRef = $ref<HTMLLIElement>() |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
let sortable: Sortable |
|
|
|
let sortable: Sortable |
|
|
|
|
|
|
|
|
|
|
@ -104,38 +125,45 @@ const icon = (table: TableType) => { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const filterQuery = $ref('') |
|
|
|
|
|
|
|
const filteredTables = $computed(() => { |
|
|
|
|
|
|
|
return tables?.value?.filter((table) => !filterQuery || table?.title.toLowerCase()?.includes(filterQuery.toLowerCase())) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({}) |
|
|
|
const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({}) |
|
|
|
|
|
|
|
|
|
|
|
const setMenuContext = (type: 'table' | 'main', value?: any) => { |
|
|
|
const setMenuContext = (type: 'table' | 'main', value?: any) => { |
|
|
|
contextMenuTarget.type = type |
|
|
|
contextMenuTarget.type = type |
|
|
|
contextMenuTarget.value = value |
|
|
|
contextMenuTarget.value = value |
|
|
|
|
|
|
|
|
|
|
|
$e('c:table:create:navdraw:right-click') |
|
|
|
$e('c:table:create:navdraw:right-click') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const renameTableDlg = ref(false) |
|
|
|
|
|
|
|
const renameTableMeta = ref() |
|
|
|
|
|
|
|
const showRenameTableDlg = (table: TableType, rightClick = false) => { |
|
|
|
|
|
|
|
$e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options') |
|
|
|
|
|
|
|
renameTableMeta.value = table |
|
|
|
|
|
|
|
renameTableDlg.value = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const reloadTables = async () => { |
|
|
|
const reloadTables = async () => { |
|
|
|
$e('a:table:refresh:navdraw') |
|
|
|
$e('a:table:refresh:navdraw') |
|
|
|
|
|
|
|
|
|
|
|
await loadTables() |
|
|
|
await loadTables() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const addTableTab = (table: TableType) => { |
|
|
|
const addTableTab = (table: TableType) => { |
|
|
|
$e('a:table:open') |
|
|
|
$e('a:table:open') |
|
|
|
|
|
|
|
|
|
|
|
addTab({ title: table.title, id: table.id, type: table.type as any }) |
|
|
|
addTab({ title: table.title, id: table.id, type: table.type as any }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const activeTable = computed(() => { |
|
|
|
function openRenameTableDialog(table: TableType, rightClick = false) { |
|
|
|
return [TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.title : null |
|
|
|
$e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isOpen = ref(true) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { close } = useDialog(DlgTableRename, { |
|
|
|
|
|
|
|
'modelValue': isOpen, |
|
|
|
|
|
|
|
'tableMeta': table, |
|
|
|
|
|
|
|
'onUpdate:modelValue': closeDialog, |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function closeDialog() { |
|
|
|
|
|
|
|
isOpen.value = false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
close(1000) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function openQuickImportDialog(type: string) { |
|
|
|
function openQuickImportDialog(type: string) { |
|
|
|
$e(`a:actions:import-${type}`) |
|
|
|
$e(`a:actions:import-${type}`) |
|
|
|
|
|
|
|
|
|
|
@ -195,30 +223,29 @@ function openTableCreateDialog() { |
|
|
|
<div |
|
|
|
<div |
|
|
|
class="pt-2 pl-2 pb-2 flex-1 overflow-y-auto flex flex-column scrollbar-thin-dull" |
|
|
|
class="pt-2 pl-2 pb-2 flex-1 overflow-y-auto flex flex-column scrollbar-thin-dull" |
|
|
|
:class="{ 'mb-[20px]': isSharedBase }" |
|
|
|
:class="{ 'mb-[20px]': isSharedBase }" |
|
|
|
style="direction: rtl" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<div |
|
|
|
|
|
|
|
style="direction: ltr" |
|
|
|
|
|
|
|
class="py-1 px-3 flex w-full align-center gap-1 cursor-pointer" |
|
|
|
|
|
|
|
@contextmenu="setMenuContext('main')" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
|
|
|
|
<div class="py-1 px-3 flex w-full align-center gap-1 cursor-pointer" @contextmenu="setMenuContext('main')"> |
|
|
|
<span class="flex-grow text-bold uppercase nc-project-tree text-gray-500 font-weight-bold"> |
|
|
|
<span class="flex-grow text-bold uppercase nc-project-tree text-gray-500 font-weight-bold"> |
|
|
|
{{ $t('objects.tables') }} |
|
|
|
{{ $t('objects.tables') }} |
|
|
|
|
|
|
|
|
|
|
|
<template v-if="tables?.length"> ({{ tables.length }}) </template> |
|
|
|
<template v-if="tables?.length"> ({{ tables.length }}) </template> |
|
|
|
</span> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div style="direction: ltr" class="flex-1"> |
|
|
|
|
|
|
|
|
|
|
|
<div class="flex-1"> |
|
|
|
<div |
|
|
|
<div |
|
|
|
class="group flex items-center gap-2 pl-5 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none" |
|
|
|
class="group flex items-center gap-2 pl-5 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none" |
|
|
|
@click="openTableCreateDialog" |
|
|
|
@click="openTableCreateDialog" |
|
|
|
> |
|
|
|
> |
|
|
|
<MdiPlus /> |
|
|
|
<MdiPlus /> |
|
|
|
|
|
|
|
|
|
|
|
<span class="text-gray-500 group-hover:(text-primary/100) flex-1">{{ $t('tooltip.addTable') }}</span> |
|
|
|
<span class="text-gray-500 group-hover:(text-primary/100) flex-1">{{ $t('tooltip.addTable') }}</span> |
|
|
|
|
|
|
|
|
|
|
|
<a-dropdown :trigger="['click']" @click.stop> |
|
|
|
<a-dropdown :trigger="['click']" @click.stop> |
|
|
|
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100" /> |
|
|
|
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100" /> |
|
|
|
|
|
|
|
|
|
|
|
<template #overlay> |
|
|
|
<template #overlay> |
|
|
|
<a-menu class="nc-add-project-menu !py-0 ml-6 rounded text-sm"> |
|
|
|
<a-menu class="!py-0 rounded text-sm"> |
|
|
|
<a-menu-item-group title="QUICK IMPORT FROM" class="!px-0 !mx-0"> |
|
|
|
<a-menu-item-group title="QUICK IMPORT FROM" class="!px-0 !mx-0"> |
|
|
|
<a-menu-item |
|
|
|
<a-menu-item |
|
|
|
v-if="isUIAllowed('airtableImport')" |
|
|
|
v-if="isUIAllowed('airtableImport')" |
|
|
@ -321,23 +348,22 @@ function openTableCreateDialog() { |
|
|
|
<MdiMenuIcon class="transition-opacity opacity-0 group-hover:opacity-100" /> |
|
|
|
<MdiMenuIcon class="transition-opacity opacity-0 group-hover:opacity-100" /> |
|
|
|
|
|
|
|
|
|
|
|
<template #overlay> |
|
|
|
<template #overlay> |
|
|
|
<a-menu class="cursor-pointer"> |
|
|
|
<a-menu class="!py-0 rounded text-sm"> |
|
|
|
<a-menu-item |
|
|
|
<a-menu-item |
|
|
|
v-if="isUIAllowed('table-rename')" |
|
|
|
v-if="isUIAllowed('table-rename')" |
|
|
|
v-t="['c:table:rename']" |
|
|
|
v-t="['c:table:rename']" |
|
|
|
class="!text-xs" |
|
|
|
@click="openRenameTableDialog(table)" |
|
|
|
@click="showRenameTableDlg(table)" |
|
|
|
|
|
|
|
><div>{{ $t('general.rename') }}</div></a-menu-item |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
{{ $t('general.rename') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item |
|
|
|
<a-menu-item v-if="isUIAllowed('table-delete')" v-t="['c:table:delete']" @click="deleteTable(table)"> |
|
|
|
v-if="isUIAllowed('table-delete')" |
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
v-t="['c:table:delete']" |
|
|
|
{{ $t('general.delete') }} |
|
|
|
class="!text-xs" |
|
|
|
</div> |
|
|
|
@click="deleteTable(table)" |
|
|
|
</a-menu-item> |
|
|
|
> |
|
|
|
|
|
|
|
{{ $t('general.delete') }}</a-menu-item |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
</a-menu> |
|
|
|
</a-menu> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</a-dropdown> |
|
|
|
</a-dropdown> |
|
|
@ -350,35 +376,43 @@ function openTableCreateDialog() { |
|
|
|
<div class="flex flex-col align-center"> |
|
|
|
<div class="flex flex-col align-center"> |
|
|
|
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> |
|
|
|
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> |
|
|
|
|
|
|
|
|
|
|
|
<a-button type="primary" @click.stop="openTableCreateDialog">{{ $t('tooltip.addTable') }}</a-button> |
|
|
|
<a-button type="primary" @click.stop="openTableCreateDialog"> |
|
|
|
|
|
|
|
{{ $t('tooltip.addTable') }} |
|
|
|
|
|
|
|
</a-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</a-card> |
|
|
|
</a-card> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<template v-if="!isLocked" #overlay> |
|
|
|
<template v-if="!isLocked" #overlay> |
|
|
|
<a-menu class="cursor-pointer"> |
|
|
|
<a-menu class="!py-0 rounded text-sm"> |
|
|
|
<template v-if="contextMenuTarget.type === 'table'"> |
|
|
|
<template v-if="contextMenuTarget.type === 'table'"> |
|
|
|
<a-menu-item |
|
|
|
<a-menu-item |
|
|
|
v-if="isUIAllowed('table-rename')" |
|
|
|
v-if="isUIAllowed('table-rename')" |
|
|
|
v-t="['c:table:rename']" |
|
|
|
v-t="['c:table:rename']" |
|
|
|
class="!text-xs" |
|
|
|
@click="openRenameTableDialog(contextMenuTarget.value)" |
|
|
|
@click="showRenameTableDlg(contextMenuTarget.value)" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
{{ $t('general.rename') }} |
|
|
|
{{ $t('general.rename') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
</a-menu-item> |
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item |
|
|
|
<a-menu-item |
|
|
|
v-if="isUIAllowed('table-delete')" |
|
|
|
v-if="isUIAllowed('table-delete')" |
|
|
|
v-t="['c:table:delete']" |
|
|
|
v-t="['c:table:delete']" |
|
|
|
class="!text-xs" |
|
|
|
|
|
|
|
@click="deleteTable(contextMenuTarget.value)" |
|
|
|
@click="deleteTable(contextMenuTarget.value)" |
|
|
|
> |
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
{{ $t('general.delete') }} |
|
|
|
{{ $t('general.delete') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
</a-menu-item> |
|
|
|
</a-menu-item> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
|
|
<template v-else> |
|
|
|
<template v-else> |
|
|
|
<a-menu-item v-t="['c:table:reload']" class="!text-xs" @click="reloadTables"> |
|
|
|
<a-menu-item v-t="['c:table:reload']" @click="reloadTables"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
{{ $t('general.reload') }} |
|
|
|
{{ $t('general.reload') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
</a-menu-item> |
|
|
|
</a-menu-item> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</a-menu> |
|
|
|
</a-menu> |
|
|
@ -388,15 +422,8 @@ function openTableCreateDialog() { |
|
|
|
<a-divider class="mt-0 mb-0" /> |
|
|
|
<a-divider class="mt-0 mb-0" /> |
|
|
|
|
|
|
|
|
|
|
|
<div class="items-center flex justify-center p-2"> |
|
|
|
<div class="items-center flex justify-center p-2"> |
|
|
|
<!-- |
|
|
|
|
|
|
|
Todo : move the component |
|
|
|
|
|
|
|
<GithubStarButton /> |
|
|
|
|
|
|
|
--> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<GeneralShareBaseButton class="!mr-0" /> |
|
|
|
<GeneralShareBaseButton class="!mr-0" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<DlgTableRename v-if="renameTableMeta" v-model="renameTableDlg" :table-meta="renameTableMeta" /> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
|
@ -472,4 +499,28 @@ function openTableCreateDialog() { |
|
|
|
@apply pr-6 !border-0; |
|
|
|
@apply pr-6 !border-0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-dropdown-menu-item-group-title) { |
|
|
|
|
|
|
|
@apply border-b-1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-dropdown-menu-item-group-list) { |
|
|
|
|
|
|
|
@apply !mx-0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-dropdown-menu-item-group-title) { |
|
|
|
|
|
|
|
@apply border-b-1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-dropdown-menu-item-group-list) { |
|
|
|
|
|
|
|
@apply m-0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-dropdown-menu-item) { |
|
|
|
|
|
|
|
@apply !py-0 active:(ring ring-pink-500); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-dropdown-menu-title-content) { |
|
|
|
|
|
|
|
@apply !p-0; |
|
|
|
|
|
|
|
} |
|
|
|
</style> |
|
|
|
</style> |
|
|
|