Browse Source

Merge pull request #7818 from nocodb/nc-feat/open-base-table-view-in-new-tab

Nc feat(nc-gui): open base/table/view in a new tab on pressing Ctrl & clicking on them
pull/7835/head
Raju Udava 9 months ago committed by GitHub
parent
commit
078e5afc08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      packages/nc-gui/components/dashboard/Sidebar/TopSection.vue
  2. 23
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  3. 9
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  4. 19
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  5. 30
      packages/nc-gui/composables/useTableNew.ts
  6. 80
      packages/nc-gui/store/views.ts
  7. 11
      packages/nc-gui/store/workspace.ts
  8. 8
      packages/nc-gui/utils/urlUtils.ts

6
packages/nc-gui/components/dashboard/Sidebar/TopSection.vue

@ -6,6 +6,8 @@ const { isUIAllowed } = useRoles()
const { appInfo } = useGlobal() const { appInfo } = useGlobal()
const { meta: metaKey, ctrlKey } = useMagicKeys()
const { isWorkspaceLoading, isWorkspaceSettingsPageOpened } = storeToRefs(workspaceStore) const { isWorkspaceLoading, isWorkspaceSettingsPageOpened } = storeToRefs(workspaceStore)
const { navigateToWorkspaceSettings } = workspaceStore const { navigateToWorkspaceSettings } = workspaceStore
@ -15,8 +17,10 @@ const { isSharedBase } = storeToRefs(baseStore)
const isCreateProjectOpen = ref(false) const isCreateProjectOpen = ref(false)
const navigateToSettings = () => { const navigateToSettings = () => {
const cmdOrCtrl = isMac() ? metaKey.value : ctrlKey.value
// TODO: Handle cloud case properly // TODO: Handle cloud case properly
navigateToWorkspaceSettings() navigateToWorkspaceSettings('', cmdOrCtrl)
// if (appInfo.value.baseHostName) { // if (appInfo.value.baseHostName) {
// window.location.href = `https://app.${appInfo.value.baseHostName}/dashboard` // window.location.href = `https://app.${appInfo.value.baseHostName}/dashboard`

23
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -16,6 +16,7 @@ import {
h, h,
inject, inject,
navigateTo, navigateTo,
navigateToBlankTargetOpenOption,
openLink, openLink,
ref, ref,
resolveComponent, resolveComponent,
@ -26,6 +27,7 @@ import {
useDialog, useDialog,
useGlobal, useGlobal,
useI18n, useI18n,
useMagicKeys,
useNuxtApp, useNuxtApp,
useRoles, useRoles,
useRouter, useRouter,
@ -71,6 +73,8 @@ const { orgRoles, isUIAllowed } = useRoles()
useTabs() useTabs()
const { meta: metaKey, ctrlKey } = useMagicKeys()
const editMode = ref(false) const editMode = ref(false)
const tempTitle = ref('') const tempTitle = ref('')
@ -253,12 +257,29 @@ const onProjectClick = async (base: NcProject, ignoreNavigation?: boolean, toggl
if (!base) { if (!base) {
return return
} }
const cmdOrCtrl = isMac() ? metaKey.value : ctrlKey.value
if (!toggleIsExpanded) $e('c:base:open') if (!toggleIsExpanded && !cmdOrCtrl) $e('c:base:open')
ignoreNavigation = isMobileMode.value || ignoreNavigation ignoreNavigation = isMobileMode.value || ignoreNavigation
toggleIsExpanded = isMobileMode.value || toggleIsExpanded toggleIsExpanded = isMobileMode.value || toggleIsExpanded
if (cmdOrCtrl && !ignoreNavigation) {
await navigateTo(
`${cmdOrCtrl ? '#' : ''}${baseUrl({
id: base.id!,
type: 'database',
isSharedBase: isSharedBase.value,
})}`,
cmdOrCtrl
? {
open: navigateToBlankTargetOpenOption,
}
: undefined,
)
return
}
if (toggleIsExpanded) { if (toggleIsExpanded) {
base.isExpanded = !base.isExpanded base.isExpanded = !base.isExpanded
} else { } else {

9
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -4,7 +4,7 @@ import { toRef } from '@vue/reactivity'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs } from '#imports' import { ProjectRoleInj, TreeViewInj, useMagicKeys, useNuxtApp, useRoles, useTabs } from '#imports'
import type { SidebarTableNode } from '~/lib' import type { SidebarTableNode } from '~/lib'
const props = withDefaults( const props = withDefaults(
@ -39,6 +39,8 @@ useTableNew({
baseId: base.value.id!, baseId: base.value.id!,
}) })
const { meta: metaKey, ctrlKey } = useMagicKeys()
const baseRole = inject(ProjectRoleInj) const baseRole = inject(ProjectRoleInj)
provide(SidebarTableInj, table) provide(SidebarTableInj, table)
@ -106,6 +108,11 @@ const onExpand = async () => {
} }
const onOpenTable = async () => { const onOpenTable = async () => {
if (isMac() ? metaKey.value : ctrlKey.value) {
await _openTable(table.value, true)
return
}
isLoading.value = true isLoading.value = true
try { try {
await _openTable(table.value) await _openTable(table.value)

19
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -9,6 +9,7 @@ import {
message, message,
onKeyStroke, onKeyStroke,
useDebounceFn, useDebounceFn,
useMagicKeys,
useNuxtApp, useNuxtApp,
useRoles, useRoles,
useVModel, useVModel,
@ -52,6 +53,8 @@ const { activeView } = storeToRefs(useViewsStore())
const { getMeta } = useMetas() const { getMeta } = useMetas()
const { meta: metaKey, ctrlKey } = useMagicKeys()
const table = computed(() => props.table) const table = computed(() => props.table)
const injectedTable = ref(table.value) const injectedTable = ref(table.value)
@ -80,11 +83,21 @@ const _title = ref<string | undefined>()
/** Debounce click handler, so we can potentially enable editing view name {@see onDblClick} */ /** Debounce click handler, so we can potentially enable editing view name {@see onDblClick} */
const onClick = useDebounceFn(() => { const onClick = useDebounceFn(() => {
if (isEditing.value || isStopped.value) return
emits('changeView', vModel.value) emits('changeView', vModel.value)
}, 250) }, 250)
const handleOnClick = () => {
if (isEditing.value || isStopped.value) return
const cmdOrCtrl = isMac() ? metaKey.value : ctrlKey.value
if (cmdOrCtrl) {
emits('changeView', vModel.value)
} else {
onClick()
}
}
/** Enable editing view name on dbl click */ /** Enable editing view name on dbl click */
function onDblClick() { function onDblClick() {
if (isMobileMode.value) return if (isMobileMode.value) return
@ -209,7 +222,7 @@ watch(isDropdownOpen, async () => {
}" }"
:data-testid="`view-sidebar-view-${vModel.alias || vModel.title}`" :data-testid="`view-sidebar-view-${vModel.alias || vModel.title}`"
@dblclick.stop="onDblClick" @dblclick.stop="onDblClick"
@click="onClick" @click.prevent="handleOnClick"
> >
<div v-e="['a:view:open', { view: vModel.type }]" class="text-sm flex items-center w-full gap-1" data-testid="view-item"> <div v-e="['a:view:open', { view: vModel.type }]" class="text-sm flex items-center w-full gap-1" data-testid="view-item">
<div <div

30
packages/nc-gui/composables/useTableNew.ts

@ -10,6 +10,7 @@ import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
generateUniqueTitle as generateTitle, generateUniqueTitle as generateTitle,
message, message,
navigateToBlankTargetOpenOption,
reactive, reactive,
storeToRefs, storeToRefs,
useBase, useBase,
@ -60,7 +61,7 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
const tables = computed(() => baseTables.value.get(param.baseId) || []) const tables = computed(() => baseTables.value.get(param.baseId) || [])
const base = computed(() => bases.value.get(param.baseId)) const base = computed(() => bases.value.get(param.baseId))
const openTable = async (table: SidebarTableNode) => { const openTable = async (table: SidebarTableNode, cmdOrCtrl: boolean = false) => {
if (!table.base_id) return if (!table.base_id) return
let base = bases.value.get(table.base_id) let base = bases.value.get(table.base_id)
@ -86,7 +87,14 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
const navigateToTable = async () => { const navigateToTable = async () => {
if (openedViewsTab.value === 'view') { if (openedViewsTab.value === 'view') {
await navigateTo(`/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`) await navigateTo(
`${cmdOrCtrl ? '#' : ''}/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`,
cmdOrCtrl
? {
open: navigateToBlankTargetOpenOption,
}
: undefined,
)
} }
table.isViewsLoading = true table.isViewsLoading = true
@ -99,7 +107,16 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
// find the default view and navigate to it, if not found navigate to the first one // find the default view and navigate to it, if not found navigate to the first one
const defaultView = views.find((v) => v.is_default) || views[0] const defaultView = views.find((v) => v.is_default) || views[0]
await navigateTo(`/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${openedViewsTab.value}`) await navigateTo(
`${cmdOrCtrl ? '#' : ''}/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${
openedViewsTab.value
}`,
cmdOrCtrl
? {
open: navigateToBlankTargetOpenOption,
}
: undefined,
)
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -119,8 +136,11 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v
table.isMetaLoading = false table.isMetaLoading = false
} }
} }
if (cmdOrCtrl) {
await Promise.all([navigateToTable(), loadTableMeta()]) await navigateToTable()
} else {
await Promise.all([navigateToTable(), loadTableMeta()])
}
} }
const createTable = async () => { const createTable = async () => {

80
packages/nc-gui/store/views.ts

@ -1,6 +1,7 @@
import type { FilterType, SortType, ViewType, ViewTypes } from 'nocodb-sdk' import type { FilterType, SortType, ViewType, ViewTypes } from 'nocodb-sdk'
import { acceptHMRUpdate, defineStore } from 'pinia' import { acceptHMRUpdate, defineStore } from 'pinia'
import type { ViewPageType } from '~/lib' import type { ViewPageType } from '~/lib'
import { navigateToBlankTargetOpenOption, useMagicKeys } from '#imports'
export const useViewsStore = defineStore('viewsStore', () => { export const useViewsStore = defineStore('viewsStore', () => {
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
@ -27,6 +28,8 @@ export const useViewsStore = defineStore('viewsStore', () => {
const { activeWorkspaceId } = storeToRefs(useWorkspace()) const { activeWorkspaceId } = storeToRefs(useWorkspace())
const { meta: metaKey, ctrlKey } = useMagicKeys()
const recentViews = computed<RecentView[]>(() => const recentViews = computed<RecentView[]>(() =>
allRecentViews.value.filter((f) => f.workspaceId === activeWorkspaceId.value).splice(0, 10), allRecentViews.value.filter((f) => f.workspaceId === activeWorkspaceId.value).splice(0, 10),
) )
@ -216,6 +219,8 @@ export const useViewsStore = defineStore('viewsStore', () => {
hardReload?: boolean hardReload?: boolean
doNotSwitchTab?: boolean doNotSwitchTab?: boolean
}) => { }) => {
const cmdOrCtrl = isMac() ? metaKey.value : ctrlKey.value
const routeName = 'index-typeOrId-baseId-index-index-viewId-viewTitle-slugs' const routeName = 'index-typeOrId-baseId-index-index-viewId-viewTitle-slugs'
let baseIdOrBaseId = baseId let baseIdOrBaseId = baseId
@ -231,29 +236,64 @@ export const useViewsStore = defineStore('viewsStore', () => {
router.currentRoute.value.query.page && router.currentRoute.value.query.page &&
router.currentRoute.value.query.page === 'fields' router.currentRoute.value.query.page === 'fields'
) { ) {
await router.push({ if (cmdOrCtrl) {
name: routeName, await navigateTo(
params: { router.resolve({
viewTitle: view.id || '', name: routeName,
viewId: tableId, params: {
baseId: baseIdOrBaseId, viewTitle: view.id || '',
slugs, viewId: tableId,
}, baseId: baseIdOrBaseId,
query: router.currentRoute.value.query, slugs,
}) },
query: router.currentRoute.value.query,
}).href,
{
open: navigateToBlankTargetOpenOption,
},
)
} else {
await router.push({
name: routeName,
params: {
viewTitle: view.id || '',
viewId: tableId,
baseId: baseIdOrBaseId,
slugs,
},
query: router.currentRoute.value.query,
})
}
} else { } else {
await router.push({ if (cmdOrCtrl) {
name: routeName, await navigateTo(
params: { router.resolve({
viewTitle: view.id || '', name: routeName,
viewId: tableId, params: {
baseId: baseIdOrBaseId, viewTitle: view.id || '',
slugs, viewId: tableId,
}, baseId: baseIdOrBaseId,
}) slugs,
},
}).href,
{
open: navigateToBlankTargetOpenOption,
},
)
} else {
await router.push({
name: routeName,
params: {
viewTitle: view.id || '',
viewId: tableId,
baseId: baseIdOrBaseId,
slugs,
},
})
}
} }
if (hardReload) { if (!cmdOrCtrl && hardReload) {
await router await router
.replace({ .replace({
name: routeName, name: routeName,

11
packages/nc-gui/store/workspace.ts

@ -199,8 +199,15 @@ export const useWorkspace = defineStore('workspaceStore', () => {
}) })
} }
const navigateToWorkspaceSettings = async () => { const navigateToWorkspaceSettings = async (_, cmdOrCtrl) => {
navigateTo('/account/users') await navigateTo(
`${cmdOrCtrl ? '#' : ''}/account/users`,
cmdOrCtrl
? {
open: navigateToBlankTargetOpenOption,
}
: undefined,
)
} }
function setLoadingState(isLoading = false) { function setLoadingState(isLoading = false) {

8
packages/nc-gui/utils/urlUtils.ts

@ -35,3 +35,11 @@ export const openLink = (path: string, baseURL?: string, target = '_blank') => {
const url = new URL(path, baseURL) const url = new URL(path, baseURL)
window.open(url.href, target, 'noopener,noreferrer') window.open(url.href, target, 'noopener,noreferrer')
} }
export const navigateToBlankTargetOpenOption = {
target: '_blank',
windowFeatures: {
noopener: true,
noreferrer: true,
},
}

Loading…
Cancel
Save