|
|
@ -45,11 +45,11 @@ const [searchActive, toggleSearchActive] = useToggle() |
|
|
|
|
|
|
|
|
|
|
|
const toggleDialog = inject(ToggleDialogInj, () => {}) |
|
|
|
const toggleDialog = inject(ToggleDialogInj, () => {}) |
|
|
|
|
|
|
|
|
|
|
|
let key = $ref(0) |
|
|
|
const keys = $ref<Record<string, number>>({}) |
|
|
|
|
|
|
|
|
|
|
|
const activeKey = ref([]) |
|
|
|
const activeKey = ref([]) |
|
|
|
|
|
|
|
|
|
|
|
const menuRef = $ref<HTMLLIElement>() |
|
|
|
const menuRefs = $ref<HTMLLIElement[]>() |
|
|
|
|
|
|
|
|
|
|
|
let filterQuery = $ref('') |
|
|
|
let filterQuery = $ref('') |
|
|
|
|
|
|
|
|
|
|
@ -69,14 +69,18 @@ const filteredTables = $computed(() => |
|
|
|
), |
|
|
|
), |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
let sortable: Sortable |
|
|
|
const sortables: Record<string, Sortable> = {} |
|
|
|
|
|
|
|
|
|
|
|
// todo: replace with vuedraggable |
|
|
|
// todo: replace with vuedraggable |
|
|
|
const initSortable = (el: Element) => { |
|
|
|
const initSortable = (el: Element) => { |
|
|
|
if (sortable) sortable.destroy() |
|
|
|
const base_id = el.getAttribute('nc-base') |
|
|
|
sortable = Sortable.create(el as HTMLLIElement, { |
|
|
|
if (!base_id) return |
|
|
|
|
|
|
|
if (sortables[base_id]) sortables[base_id].destroy() |
|
|
|
|
|
|
|
Sortable.create(el as HTMLLIElement, { |
|
|
|
handle: '.nc-drag-icon', |
|
|
|
handle: '.nc-drag-icon', |
|
|
|
onEnd: async (evt) => { |
|
|
|
onEnd: async (evt) => { |
|
|
|
|
|
|
|
const offset = tables.value.findIndex((table) => table.base_id === base_id) |
|
|
|
|
|
|
|
|
|
|
|
const { newIndex = 0, oldIndex = 0 } = evt |
|
|
|
const { newIndex = 0, oldIndex = 0 } = evt |
|
|
|
|
|
|
|
|
|
|
|
const itemEl = evt.item as HTMLLIElement |
|
|
|
const itemEl = evt.item as HTMLLIElement |
|
|
@ -106,10 +110,14 @@ const initSortable = (el: Element) => { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// update the order of the moved item |
|
|
|
// update the order of the moved item |
|
|
|
tables.value?.splice(newIndex, 0, ...tables.value?.splice(oldIndex, 1)) |
|
|
|
tables.value?.splice(newIndex + offset, 0, ...tables.value?.splice(oldIndex + offset, 1)) |
|
|
|
|
|
|
|
|
|
|
|
// force re-render the list |
|
|
|
// force re-render the list |
|
|
|
key++ |
|
|
|
if (keys[base_id]) { |
|
|
|
|
|
|
|
keys[base_id] = keys[base_id] + 1 |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
keys[base_id] = 1 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// update the item order |
|
|
|
// update the item order |
|
|
|
await $api.dbTable.reorder(item.id as string, { |
|
|
|
await $api.dbTable.reorder(item.id as string, { |
|
|
@ -121,8 +129,10 @@ const initSortable = (el: Element) => { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
watchEffect(() => { |
|
|
|
watchEffect(() => { |
|
|
|
if (menuRef) { |
|
|
|
if (menuRefs) { |
|
|
|
initSortable(menuRef) |
|
|
|
for (const menuRef of menuRefs) { |
|
|
|
|
|
|
|
initSortable(menuRef) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
@ -353,21 +363,6 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => { |
|
|
|
|
|
|
|
|
|
|
|
<a-menu-divider class="my-0" /> |
|
|
|
<a-menu-divider class="my-0" /> |
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item v-if="isUIAllowed('importRequest')" key="add-new-table" class="py-1 rounded-b"> |
|
|
|
|
|
|
|
<a |
|
|
|
|
|
|
|
v-e="['e:datasource:import-request']" |
|
|
|
|
|
|
|
href="https://github.com/nocodb/nocodb/issues/2052" |
|
|
|
|
|
|
|
target="_blank" |
|
|
|
|
|
|
|
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<MdiOpenInNew class="group-hover:text-accent" /> |
|
|
|
|
|
|
|
<!-- Request a data source you need? --> |
|
|
|
|
|
|
|
{{ $t('labels.requestDataSource') }} |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-divider class="my-0" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item-group title="Connect to new datasource" class="!px-0 !mx-0"> |
|
|
|
<a-menu-item-group title="Connect to new datasource" class="!px-0 !mx-0"> |
|
|
|
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL)"> |
|
|
|
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL)"> |
|
|
|
<div class="color-transition nc-project-menu-item group"> |
|
|
|
<div class="color-transition nc-project-menu-item group"> |
|
|
@ -394,15 +389,35 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</a-menu-item> |
|
|
|
</a-menu-item> |
|
|
|
</a-menu-item-group> |
|
|
|
</a-menu-item-group> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-divider class="my-0" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item v-if="isUIAllowed('importRequest')" key="add-new-table" class="py-1 rounded-b"> |
|
|
|
|
|
|
|
<a |
|
|
|
|
|
|
|
v-e="['e:datasource:import-request']" |
|
|
|
|
|
|
|
href="https://github.com/nocodb/nocodb/issues/2052" |
|
|
|
|
|
|
|
target="_blank" |
|
|
|
|
|
|
|
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<MdiOpenInNew class="group-hover:text-accent" /> |
|
|
|
|
|
|
|
<!-- Request a data source you need? --> |
|
|
|
|
|
|
|
{{ $t('labels.requestDataSource') }} |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
</a-menu> |
|
|
|
</a-menu> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</a-dropdown> |
|
|
|
</a-dropdown> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="transition-height duration-200 overflow-hidden"> |
|
|
|
<div class="transition-height duration-200 overflow-hidden"> |
|
|
|
<div :key="key" ref="menuRef" class="border-none sortable-list"> |
|
|
|
<div class="border-none sortable-list"> |
|
|
|
<div v-for="[index, base] of Object.entries(bases)" :key="`${base.id}-index`"> |
|
|
|
<div v-for="[index, base] of Object.entries(bases)" :key="`base-${base.id}`"> |
|
|
|
<div v-if="index === '0' && base.enabled"> |
|
|
|
<div |
|
|
|
|
|
|
|
v-if="index === '0' && base.enabled" |
|
|
|
|
|
|
|
ref="menuRefs" |
|
|
|
|
|
|
|
:key="`sortable-${base.id}-${keys[base.id || 0]}`" |
|
|
|
|
|
|
|
:nc-base="base.id" |
|
|
|
|
|
|
|
> |
|
|
|
<div |
|
|
|
<div |
|
|
|
v-for="table of tables.filter((table) => table.base_id === base.id)" |
|
|
|
v-for="table of tables.filter((table) => table.base_id === base.id)" |
|
|
|
:key="table.id" |
|
|
|
:key="table.id" |
|
|
@ -479,7 +494,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => { |
|
|
|
accordion |
|
|
|
accordion |
|
|
|
ghost |
|
|
|
ghost |
|
|
|
> |
|
|
|
> |
|
|
|
<a-collapse-panel :key="index"> |
|
|
|
<a-collapse-panel :key="`collapse-${base.id}`"> |
|
|
|
<template #header> |
|
|
|
<template #header> |
|
|
|
<div v-if="index !== '0'" class="flex items-center gap-2 text-gray-500 font-weightd"> |
|
|
|
<div v-if="index !== '0'" class="flex items-center gap-2 text-gray-500 font-weightd"> |
|
|
|
<GeneralBaseLogo :base-type="base.type" /> |
|
|
|
<GeneralBaseLogo :base-type="base.type" /> |
|
|
@ -572,75 +587,77 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => { |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</a-dropdown> |
|
|
|
</a-dropdown> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
<div ref="menuRefs" :key="`sortable-${base.id}-${keys[base.id || 0]}`" :nc-base="base.id"> |
|
|
|
v-for="table of tables.filter((table) => table.base_id === base.id)" |
|
|
|
<div |
|
|
|
:key="table.id" |
|
|
|
v-for="table of tables.filter((table) => table.base_id === base.id)" |
|
|
|
v-e="['a:table:open']" |
|
|
|
:key="table.id" |
|
|
|
:class="[ |
|
|
|
v-e="['a:table:open']" |
|
|
|
{ hidden: !filteredTables?.includes(table), active: activeTable === table.id }, |
|
|
|
:class="[ |
|
|
|
`nc-project-tree-tbl nc-project-tree-tbl-${table.title}`, |
|
|
|
{ hidden: !filteredTables?.includes(table), active: activeTable === table.id }, |
|
|
|
]" |
|
|
|
`nc-project-tree-tbl nc-project-tree-tbl-${table.title}`, |
|
|
|
class="nc-tree-item text-sm cursor-pointer group" |
|
|
|
]" |
|
|
|
:data-order="table.order" |
|
|
|
class="nc-tree-item text-sm cursor-pointer group" |
|
|
|
:data-id="table.id" |
|
|
|
:data-order="table.order" |
|
|
|
:data-testid="`tree-view-table-${table.title}`" |
|
|
|
:data-id="table.id" |
|
|
|
@click="addTableTab(table)" |
|
|
|
:data-testid="`tree-view-table-${table.title}`" |
|
|
|
> |
|
|
|
@click="addTableTab(table)" |
|
|
|
<GeneralTooltip class="pl-5 pr-3 py-2" modifier-key="Alt"> |
|
|
|
> |
|
|
|
<template #title>{{ table.table_name }}</template> |
|
|
|
<GeneralTooltip class="pl-5 pr-3 py-2" modifier-key="Alt"> |
|
|
|
<div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)"> |
|
|
|
<template #title>{{ table.table_name }}</template> |
|
|
|
<div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`"> |
|
|
|
<div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)"> |
|
|
|
<MdiDragVertical |
|
|
|
<div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`"> |
|
|
|
v-if="isUIAllowed('treeview-drag-n-drop')" |
|
|
|
<MdiDragVertical |
|
|
|
:class="`nc-child-draggable-icon-${table.title}`" |
|
|
|
v-if="isUIAllowed('treeview-drag-n-drop')" |
|
|
|
class="nc-drag-icon text-xs hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 cursor-move" |
|
|
|
:class="`nc-child-draggable-icon-${table.title}`" |
|
|
|
@click.stop.prevent |
|
|
|
class="nc-drag-icon text-xs hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 cursor-move" |
|
|
|
/> |
|
|
|
@click.stop.prevent |
|
|
|
|
|
|
|
/> |
|
|
|
<component |
|
|
|
|
|
|
|
:is="icon(table)" |
|
|
|
<component |
|
|
|
class="nc-view-icon text-xs" |
|
|
|
:is="icon(table)" |
|
|
|
:class="{ 'group-hover:hidden group-hover:text-gray-500': isUIAllowed('treeview-drag-n-drop') }" |
|
|
|
class="nc-view-icon text-xs" |
|
|
|
/> |
|
|
|
:class="{ 'group-hover:hidden group-hover:text-gray-500': isUIAllowed('treeview-drag-n-drop') }" |
|
|
|
</div> |
|
|
|
/> |
|
|
|
|
|
|
|
</div> |
|
|
|
<div class="nc-tbl-title flex-1"> |
|
|
|
|
|
|
|
<GeneralTruncateText>{{ table.title }}</GeneralTruncateText> |
|
|
|
<div class="nc-tbl-title flex-1"> |
|
|
|
|
|
|
|
<GeneralTruncateText>{{ table.title }}</GeneralTruncateText> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-dropdown |
|
|
|
|
|
|
|
v-if="!isSharedBase && (isUIAllowed('table-rename') || isUIAllowed('table-delete'))" |
|
|
|
|
|
|
|
:trigger="['click']" |
|
|
|
|
|
|
|
@click.stop |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 outline-0" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<template #overlay> |
|
|
|
|
|
|
|
<a-menu class="!py-0 rounded text-sm"> |
|
|
|
|
|
|
|
<a-menu-item |
|
|
|
|
|
|
|
v-if="isUIAllowed('table-rename')" |
|
|
|
|
|
|
|
:data-testid="`sidebar-table-rename-${table.title}`" |
|
|
|
|
|
|
|
@click="openRenameTableDialog(table, base.id)" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
{{ $t('general.rename') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item |
|
|
|
|
|
|
|
v-if="isUIAllowed('table-delete')" |
|
|
|
|
|
|
|
:data-testid="`sidebar-table-delete-${table.title}`" |
|
|
|
|
|
|
|
@click="deleteTable(table)" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
{{ $t('general.delete') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
</a-menu> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
</a-dropdown> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</GeneralTooltip> |
|
|
|
<a-dropdown |
|
|
|
</div> |
|
|
|
v-if="!isSharedBase && (isUIAllowed('table-rename') || isUIAllowed('table-delete'))" |
|
|
|
|
|
|
|
:trigger="['click']" |
|
|
|
|
|
|
|
@click.stop |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 outline-0" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<template #overlay> |
|
|
|
|
|
|
|
<a-menu class="!py-0 rounded text-sm"> |
|
|
|
|
|
|
|
<a-menu-item |
|
|
|
|
|
|
|
v-if="isUIAllowed('table-rename')" |
|
|
|
|
|
|
|
:data-testid="`sidebar-table-rename-${table.title}`" |
|
|
|
|
|
|
|
@click="openRenameTableDialog(table, base.id)" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
{{ $t('general.rename') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item |
|
|
|
|
|
|
|
v-if="isUIAllowed('table-delete')" |
|
|
|
|
|
|
|
:data-testid="`sidebar-table-delete-${table.title}`" |
|
|
|
|
|
|
|
@click="deleteTable(table)" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
{{ $t('general.delete') }} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
</a-menu> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
</a-dropdown> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</GeneralTooltip> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</a-collapse-panel> |
|
|
|
</a-collapse-panel> |
|
|
|
</a-collapse> |
|
|
|
</a-collapse> |
|
|
|