Browse Source

Merge pull request #3262 from nocodb/refactor/gui-v2-view-menu

refactor: new view layout with view menu
pull/3254/head
Pranav C 2 years ago committed by GitHub
parent
commit
2250d08e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui-v2/assets/style.scss
  2. 4
      packages/nc-gui-v2/components.d.ts
  3. 8
      packages/nc-gui-v2/components/cell/Text.vue
  4. 23
      packages/nc-gui-v2/components/smartsheet-toolbar/AddRow.vue
  5. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue
  6. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/FieldsMenu.vue
  7. 63
      packages/nc-gui-v2/components/smartsheet-toolbar/LockType.vue
  8. 8
      packages/nc-gui-v2/components/smartsheet-toolbar/Reload.vue
  9. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  10. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue
  11. 280
      packages/nc-gui-v2/components/smartsheet-toolbar/ViewActions.vue
  12. 10
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  13. 25
      packages/nc-gui-v2/components/smartsheet/Toolbar.vue
  14. 50
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue
  15. 6
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  16. 2
      packages/nc-gui-v2/components/smartsheet/sidebar/RenameableMenuItem.vue
  17. 39
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  18. 24
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/AddRow.vue
  19. 116
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue
  20. 17
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue
  21. 26
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/index.vue
  22. 11
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  23. 2
      packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue
  24. 5
      packages/nc-gui-v2/components/virtual-cell/HasMany.vue
  25. 2
      packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue
  26. 3
      packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue
  27. 14
      packages/nc-gui-v2/components/virtual-cell/components/ListChildItems.vue
  28. 14
      packages/nc-gui-v2/components/virtual-cell/components/ListItems.vue
  29. 2
      packages/nc-gui-v2/composables/useTheme/index.ts
  30. 6
      packages/nc-gui-v2/lib/enums.ts
  31. 2
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  32. 4
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue
  33. 5
      packages/nc-gui-v2/utils/viewUtils.ts
  34. 1
      packages/nc-gui/nuxt.config.js

4
packages/nc-gui-v2/assets/style.scss

@ -239,3 +239,7 @@ a {
.ant-menu-title-content { .ant-menu-title-content {
@apply !py-0; @apply !py-0;
} }
.ant-dropdown-menu-submenu-title{
@apply !pr-2;
}

4
packages/nc-gui-v2/components.d.ts vendored

@ -96,6 +96,8 @@ declare module '@vue/runtime-core' {
MdiAccount: typeof import('~icons/mdi/account')['default'] MdiAccount: typeof import('~icons/mdi/account')['default']
MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default'] MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default']
MdiAccountGroup: typeof import('~icons/mdi/account-group')['default'] MdiAccountGroup: typeof import('~icons/mdi/account-group')['default']
MdiAccountGroupIcon: typeof import('~icons/mdi/account-group-icon')['default']
MdiAccountIcon: typeof import('~icons/mdi/account-icon')['default']
MdiAccountOutline: typeof import('~icons/mdi/account-outline')['default'] MdiAccountOutline: typeof import('~icons/mdi/account-outline')['default']
MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default'] MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default']
MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default'] MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default']
@ -165,6 +167,7 @@ declare module '@vue/runtime-core' {
MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default'] MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default']
MdiLinkVariantRemove: typeof import('~icons/mdi/link-variant-remove')['default'] MdiLinkVariantRemove: typeof import('~icons/mdi/link-variant-remove')['default']
MdiLoading: typeof import('~icons/mdi/loading')['default'] MdiLoading: typeof import('~icons/mdi/loading')['default']
MdiLockOutlineIcon: typeof import('~icons/mdi/lock-outline-icon')['default']
MdiLogin: typeof import('~icons/mdi/login')['default'] MdiLogin: typeof import('~icons/mdi/login')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default'] MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiMagnify: typeof import('~icons/mdi/magnify')['default'] MdiMagnify: typeof import('~icons/mdi/magnify')['default']
@ -201,6 +204,7 @@ declare module '@vue/runtime-core' {
MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default'] MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default']
MdiTrashCan: typeof import('~icons/mdi/trash-can')['default'] MdiTrashCan: typeof import('~icons/mdi/trash-can')['default']
MdiTwitter: typeof import('~icons/mdi/twitter')['default'] MdiTwitter: typeof import('~icons/mdi/twitter')['default']
MdiUpload: typeof import('~icons/mdi/upload')['default']
MdiUploadOutline: typeof import('~icons/mdi/upload-outline')['default'] MdiUploadOutline: typeof import('~icons/mdi/upload-outline')['default']
MdiViewListOutline: typeof import('~icons/mdi/view-list-outline')['default'] MdiViewListOutline: typeof import('~icons/mdi/view-list-outline')['default']
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default'] MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']

8
packages/nc-gui-v2/components/cell/Text.vue

@ -19,6 +19,12 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>
<input v-if="editEnabled" :ref="focus" v-model="vModel" class="h-full w-full outline-none" @blur="editEnabled = false" /> <input
v-if="editEnabled"
:ref="focus"
v-model="vModel"
class="h-full w-full outline-none bg-transparent"
@blur="editEnabled = false"
/>
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>
</template> </template>

23
packages/nc-gui-v2/components/smartsheet-toolbar/AddRow.vue

@ -0,0 +1,23 @@
<script setup lang="ts">
import { OpenNewRecordFormHookInj, inject } from '#imports'
const isLocked = inject(IsLockedInj)
const openNewRecordFormHook = inject(OpenNewRecordFormHookInj)!
const onClick = () => {
if (!isLocked?.value) openNewRecordFormHook.trigger()
}
</script>
<template>
<a-tooltip placement="bottom">
<template #title> {{ $t('activity.addRow') }} </template>
<div :class="{ 'group': !isLocked, 'disabled-ring': isLocked }" class="nc-add-row flex align-center">
<MdiPlusOutline
:class="{ 'cursor-pointer text-gray-500 group-hover:(text-primary)': !isLocked, 'disabled': isLocked }"
@click="onClick"
/>
</div>
</a-tooltip>
</template>

2
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue

@ -39,7 +39,7 @@ const applyChanges = async () => await filterComp.value?.applyChanges()
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiFilterOutline /> <MdiFilterOutline />
<!-- Filter --> <!-- Filter -->
<span class="text-capitalize !text-sm font-weight-medium">{{ $t('activity.filter') }}</span> <span class="text-capitalize !text-sm font-weight-normal">{{ $t('activity.filter') }}</span>
<MdiMenuDown class="text-grey" /> <MdiMenuDown class="text-grey" />
</div> </div>
</a-button> </a-button>

2
packages/nc-gui-v2/components/smartsheet-toolbar/FieldsMenu.vue

@ -86,7 +86,7 @@ const onMove = (event: { moved: { newIndex: number } }) => {
<MdiEyeOffOutline /> <MdiEyeOffOutline />
<!-- Fields --> <!-- Fields -->
<span class="text-capitalize !text-sm font-weight-medium">{{ $t('objects.fields') }}</span> <span class="text-capitalize !text-sm font-weight-normal">{{ $t('objects.fields') }}</span>
<MdiMenuDown class="text-grey" /> <MdiMenuDown class="text-grey" />
</div> </div>

63
packages/nc-gui-v2/components/smartsheet-toolbar/LockType.vue

@ -0,0 +1,63 @@
<script setup lang="ts">
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
import MdiAccountIcon from '~icons/mdi/account'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
import { LockType } from '~/lib'
const { type, hideTick } = defineProps<{ hideTick?: boolean; type: LockType }>()
const emit = defineEmits(['select'])
const types = {
[LockType.Personal]: {
title: 'title.personalView',
icon: MdiAccountIcon,
subtitle: 'Only you can edit the view configuration. Other collaborators’ personal views are hidden by default.',
},
[LockType.Collaborative]: {
title: 'title.collabView',
icon: MdiAccountGroupIcon,
subtitle: 'Collaborators with edit permissions or higher can change the view configuration.',
},
[LockType.Locked]: {
title: 'title.lockedView',
icon: MdiLockOutlineIcon,
subtitle: 'No one can edit the view configuration until it is unlocked.',
},
}
const selectedView = inject(ActiveViewInj)
</script>
<template>
<div class="nc-locked-menu-item" @click="emit('select', type)">
<div :class="{ 'show-tick': !hideTick }">
<template v-if="!hideTick">
<MdiCheck v-if="selectedView?.lock_type === type" />
<span v-else />
</template>
<div>
<component :is="types[type].icon" class="text-gray-500" />
{{ $t(types[type].title) }}
<div class="nc-subtitle">
{{ types[type].subtitle }}
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.nc-locked-menu-item > div {
@apply p-2 items-center min-w-[350px] max-w-[350px];
&.show-tick {
@apply grid gap-2 grid-cols-[30px,auto];
}
.nc-subtitle {
@apply text-xs text-gray-500 font-weight-light;
}
}
</style>

8
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/Reload.vue → packages/nc-gui-v2/components/smartsheet-toolbar/Reload.vue

@ -3,17 +3,15 @@ import { ReloadViewDataHookInj, inject } from '#imports'
const reloadHook = inject(ReloadViewDataHookInj)! const reloadHook = inject(ReloadViewDataHookInj)!
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
const onClick = () => reloadHook.trigger() const onClick = () => reloadHook.trigger()
</script> </script>
<template> <template>
<a-tooltip :placement="isOpen ? 'bottomRight' : 'left'"> <a-tooltip placement="bottom">
<template #title> {{ $t('general.reload') }} </template> <template #title> {{ $t('general.reload') }} </template>
<div class="nc-sidebar-right-item hover:after:bg-green-500 group"> <div class="group flex align-center">
<MdiReload class="cursor-pointer group-hover:(!text-white)" @click="onClick" /> <MdiReload class="cursor-pointer text-gray-500 group-hover:(text-primary)" @click="onClick" />
</div> </div>
</a-tooltip> </a-tooltip>
</template> </template>

2
packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue

@ -126,7 +126,7 @@ onMounted(() => {
<div class="flex items-center gap-1" @click="genShareLink"> <div class="flex items-center gap-1" @click="genShareLink">
<MdiOpenInNewIcon /> <MdiOpenInNewIcon />
<!-- Share View --> <!-- Share View -->
<span class="!text-sm font-weight-medium"> {{ $t('activity.shareView') }}</span> <span class="!text-sm font-weight-normal"> {{ $t('activity.shareView') }}</span>
</div> </div>
</a-button> </a-button>

2
packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue

@ -40,7 +40,7 @@ watch(
><div class="flex items-center gap-1"> ><div class="flex items-center gap-1">
<MdiSortIcon /> <MdiSortIcon />
<!-- Sort --> <!-- Sort -->
<span class="text-capitalize !text-sm font-weight-medium">{{ $t('activity.sort') }}</span> <span class="text-capitalize !text-sm font-weight-normal">{{ $t('activity.sort') }}</span>
<MdiMenuDownIcon class="text-grey" /> <MdiMenuDownIcon class="text-grey" />
</div> </div>
</a-button> </a-button>

280
packages/nc-gui-v2/components/smartsheet-toolbar/ViewActions.vue

@ -0,0 +1,280 @@
<script lang="ts" setup>
import * as XLSX from 'xlsx'
import { ExportTypes } from 'nocodb-sdk'
import FileSaver from 'file-saver'
import { message } from 'ant-design-vue'
import { LockType } from '~/lib'
import { viewIcons } from '~/utils'
import {
ActiveViewInj,
FieldsInj,
IsLockedInj,
IsPublicInj,
MetaInj,
extractSdkResponseErrorMsg,
inject,
ref,
useNuxtApp,
useProject,
useUIPermission,
} from '#imports'
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
import MdiAccountIcon from '~icons/mdi/account'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
const sharedViewListDlg = ref(false)
const isPublicView = inject(IsPublicInj, ref(false))
const isView = false
const { project } = useProject()
const { $api, $e } = useNuxtApp()
const meta = inject(MetaInj)
const fields = inject(FieldsInj, ref([]))
const selectedView = inject(ActiveViewInj)
const isLocked = inject(IsLockedInj)
const showWebhookDrawer = ref(false)
const quickImportDialog = ref(false)
const { isUIAllowed } = useUIPermission()
const exportFile = async (exportType: ExportTypes) => {
let offset = 0
let c = 1
const responseType = exportType === ExportTypes.EXCEL ? 'base64' : 'blob'
try {
while (!isNaN(offset) && offset > -1) {
let res
if (isPublicView.value) {
const { exportFile: sharedViewExportFile } = useSharedView()
res = await sharedViewExportFile(fields.value, offset, exportType, responseType)
} else {
res = await $api.dbViewRow.export(
'noco',
project?.value.title as string,
meta?.value.title as string,
selectedView?.value.title as string,
exportType,
{
responseType,
query: {
offset,
},
} as any,
)
}
const { data, headers } = res
if (exportType === ExportTypes.EXCEL) {
const workbook = XLSX.read(data, { type: 'base64' })
XLSX.writeFile(workbook, `${meta?.value.title}_exported_${c++}.xlsx`)
} else if (exportType === ExportTypes.CSV) {
const blob = new Blob([data], { type: 'text/plain;charset=utf-8' })
FileSaver.saveAs(blob, `${meta?.value.title}_exported_${c++}.csv`)
}
offset = +headers['nc-export-offset']
if (offset > -1) {
message.info('Downloading more files')
} else {
message.success('Successfully exported all table data')
}
}
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const Icon = computed(() => {
switch ((selectedView?.value as any)?.lock_type) {
case LockType.Personal:
return MdiAccountIcon
case LockType.Locked:
return MdiLockOutlineIcon
case LockType.Collaborative:
default:
return MdiAccountGroupIcon
}
})
async function changeLockType(type: LockType) {
$e('a:grid:lockmenu', { lockType: type })
if (!selectedView?.value) return
if (type === 'personal') {
return message.info('Coming soon')
}
try {
;(selectedView.value as any).lock_type = type
$api.dbView.update(selectedView.value.id as string, {
lock_type: type,
})
message.success(`Successfully Switched to ${type} view`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
</script>
<template>
<div>
<a-dropdown :trigger="['click']">
<a-button v-t="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center">
<component
:is="viewIcons[selectedView?.type].icon"
class="nc-view-icon group-hover:hidden"
:style="{ color: viewIcons[selectedView?.type].color }"
/>
<span class="!text-sm font-weight-normal">{{ selectedView?.title }}</span>
<component :is="Icon" class="text-gray-500" />
<MdiMenuDown class="text-grey" />
</div>
</a-button>
<template #overlay>
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded">
<a-menu-item-group>
<a-sub-menu
v-if="isUIAllowed('view-type')"
key="lock-type"
class="scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0"
>
<template #title>
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group px-0 !py-0">
<SmartsheetToolbarLockType hide-tick :type="selectedView?.lock_type || LockType.Collaborative" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-menu-item @click="changeLockType(LockType.Collaborative)">
<SmartsheetToolbarLockType :type="LockType.Collaborative" />
</a-menu-item>
<a-menu-item @click="changeLockType(LockType.Locked)">
<SmartsheetToolbarLockType :type="LockType.Locked" />
</a-menu-item>
<a-menu-item @click="changeLockType(LockType.Personal)">
<SmartsheetToolbarLockType :type="LockType.Personal" />
</a-menu-item>
</a-sub-menu>
<a-menu-divider />
<a-sub-menu key="download">
<template #title>
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiDownload class="group-hover:text-accent text-gray-500" />
Download
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-menu-item>
<div v-t="['a:actions:download-csv']" class="nc-project-menu-item" @click="exportFile(ExportTypes.CSV)">
<MdiDownloadOutline class="text-gray-500" />
<!-- Download as CSV -->
{{ $t('activity.downloadCSV') }}
</div>
</a-menu-item>
<a-menu-item>
<div v-t="['a:actions:download-excel']" class="nc-project-menu-item" @click="exportFile(ExportTypes.EXCEL)">
<MdiDownloadOutline class="text-gray-500" />
<!-- Download as XLSX -->
{{ $t('activity.downloadExcel') }}
</div>
</a-menu-item>
</a-sub-menu>
<template v-if="isUIAllowed('csvImport') && !isView && !isPublicView">
<a-sub-menu key="upload">
<template #title>
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiUpload class="group-hover:text-accent text-gray-500" />
Upload
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-menu-item>
<div
v-if="isUIAllowed('csvImport') && !isView && !isPublicView"
v-t="['a:actions:upload-csv']"
class="nc-project-menu-item"
:class="{ disabled: isLocked }"
@click="!isLocked ? (quickImportDialog = true) : {}"
>
<MdiUploadOutline class="text-gray-500" />
<!-- Upload CSV -->
{{ $t('activity.uploadCSV') }}
</div>
</a-menu-item>
</a-sub-menu>
</template>
<a-menu-divider />
<a-menu-item>
<div
v-if="isUIAllowed('SharedViewList') && !isView && !isPublicView"
v-t="['a:actions:shared-view-list']"
class="py-2 flex gap-2 items-center"
@click="sharedViewListDlg = true"
>
<MdiViewListOutline class="text-gray-500" />
<!-- Shared View List -->
{{ $t('activity.listSharedView') }}
</div>
</a-menu-item>
<a-menu-item>
<div
v-if="isUIAllowed('webhook') && !isView && !isPublicView"
v-t="['c:actions:webhook']"
class="py-2 flex gap-2 items-center"
@click="showWebhookDrawer = true"
>
<MdiHook class="text-gray-500" />
{{ $t('objects.webhooks') }}
</div>
</a-menu-item>
</a-menu-item-group>
</a-menu>
</template>
</a-dropdown>
<DlgQuickImport v-if="quickImportDialog" v-model="quickImportDialog" import-type="csv" :import-only="true" />
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
<a-modal v-model:visible="sharedViewListDlg" title="Shared view list" width="max(900px,60vw)" :footer="null">
<SmartsheetToolbarSharedViewList v-if="sharedViewListDlg" />
</a-modal>
</div>
</template>
<style scoped>
:deep(.ant-dropdown-menu-submenu-title) {
@apply py-0;
}
:deep(.ant-dropdown-menu-item-group-title) {
@apply hidden;
}
</style>

10
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -314,7 +314,7 @@ const onNavigate = (dir: NavigateDir) => {
<a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']"> <a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']">
<table <table
ref="smartTable" ref="smartTable"
class="xc-row-table nc-grid backgroundColorDefault !h-auto" class="xc-row-table nc-grid backgroundColorDefault !h-auto bg-white"
@contextmenu.prevent="contextMenu = true" @contextmenu.prevent="contextMenu = true"
> >
<thead> <thead>
@ -475,11 +475,11 @@ const onNavigate = (dir: NavigateDir) => {
<td <td
v-t="['c:row:add:grid-bottom']" v-t="['c:row:add:grid-bottom']"
:colspan="visibleColLength + 1" :colspan="visibleColLength + 1"
class="text-left pointer nc-grid-add-new-cell" class="text-left pointer nc-grid-add-new-cell cursor-pointer"
@click="addEmptyRow()" @click="addEmptyRow()"
> >
<div class="px-2 w-full flex items-center text-gray-500"> <div class="px-2 w-full flex items-center text-gray-500">
<MdiPlus class="text-pint-500 text-xs ml-2" /> <MdiPlus class="text-pint-500 text-xs ml-2 text-primary" />
<span class="ml-1"> <span class="ml-1">
{{ $t('activity.addRow') }} {{ $t('activity.addRow') }}
@ -633,4 +633,8 @@ const onNavigate = (dir: NavigateDir) => {
} }
} }
} }
tbody tr:hover {
@apply bg-gray-100 bg-opacity-50;
}
</style> </style>

25
packages/nc-gui-v2/components/smartsheet/Toolbar.vue

@ -1,13 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { IsPublicInj, useSmartsheetStoreOrThrow } from '#imports' import { IsPublicInj, useSmartsheetStoreOrThrow } from '#imports'
import ToggleDrawer from '~/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue'
const { isGrid, isForm, isGallery } = useSmartsheetStoreOrThrow() const { isGrid, isForm, isGallery } = useSmartsheetStoreOrThrow()
const { allowCSVDownload } = useSharedView()
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
const { allowCSVDownload } = useSharedView()
const { isOpen } = useSidebar()
</script> </script>
<template> <template>
<div class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[var(--toolbar-height)] px-2 border-b" style="z-index: 7"> <div
class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[var(--toolbar-height)] px-2 border-b overflow-x-hidden"
style="z-index: 7"
>
<SmartsheetToolbarViewActions
v-if="(isGrid && !isPublic) || (isGrid && isPublic && allowCSVDownload)"
:show-system-fields="false"
class="ml-1"
/>
<SmartsheetToolbarFieldsMenu v-if="isGrid || isGallery" :show-system-fields="false" class="ml-1" /> <SmartsheetToolbarFieldsMenu v-if="isGrid || isGallery" :show-system-fields="false" class="ml-1" />
<SmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery" /> <SmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery" />
@ -16,9 +28,14 @@ const isPublic = inject(IsPublicInj, ref(false))
<SmartsheetToolbarShareView v-if="(isForm || isGrid) && !isPublic" /> <SmartsheetToolbarShareView v-if="(isForm || isGrid) && !isPublic" />
<SmartsheetToolbarMoreActions v-if="(isGrid && !isPublic) || (isGrid && isPublic && allowCSVDownload)" />
<div class="flex-1" /> <div class="flex-1" />
<SmartsheetToolbarSearchData v-if="(isGrid || isGallery) && !isPublic" class="shrink mr-2" />
<SmartsheetToolbarReload class="mx-1" />
<SmartsheetToolbarAddRow class="mx-1" />
<SmartsheetToolbarSearchData v-if="(isGrid || isGallery) && !isPublic" class="shrink mr-2 ml-2" />
<ToggleDrawer v-if="!isOpen" class="mr-2" />
</div> </div>
</template> </template>

50
packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { ref, useNuxtApp } from '#imports' import { ref } from '#imports'
import { viewIcons } from '~/utils' import { viewIcons } from '~/utils'
interface Emits { interface Emits {
@ -9,33 +9,20 @@ interface Emits {
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const { $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
const isView = ref(false) const isView = ref(false)
let showApiSnippet = $ref(false)
const showWebhookDrawer = ref(false) const showWebhookDrawer = ref(false)
function onApiSnippet() {
// get API snippet
showApiSnippet = true
$e('a:view:api-snippet')
}
function onWebhooks() {
showWebhookDrawer.value = true
}
function onOpenModal(type: ViewTypes, title = '') { function onOpenModal(type: ViewTypes, title = '') {
emits('openModal', { type, title }) emits('openModal', { type, title })
} }
</script> </script>
<template> <template>
<a-menu :selected-keys="[]" class="flex-1 flex flex-col"> <a-menu :selected-keys="[]" class="flex flex-col">
<div class="flex-1"></div>
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')"> <div v-if="isUIAllowed('virtualViewsCreateOrEdit')">
<h3 class="px-3 py-1 text-xs font-semibold flex items-center gap-4 text-gray-500"> <h3 class="px-3 py-1 text-xs font-semibold flex items-center gap-4 text-gray-500">
{{ $t('activity.createView') }} {{ $t('activity.createView') }}
@ -52,7 +39,7 @@ function onOpenModal(type: ViewTypes, title = '') {
</template> </template>
<div class="text-xs flex items-center h-full w-full gap-2"> <div class="text-xs flex items-center h-full w-full gap-2">
<component :is="viewIcons[ViewTypes.GRID].icon" :class="`text-${viewIcons[ViewTypes.GRID].color}`" /> <component :is="viewIcons[ViewTypes.GRID].icon" :style="{ color: viewIcons[ViewTypes.GRID].color }" />
<div>{{ $t('objects.viewType.grid') }}</div> <div>{{ $t('objects.viewType.grid') }}</div>
@ -74,7 +61,7 @@ function onOpenModal(type: ViewTypes, title = '') {
</template> </template>
<div class="text-xs flex items-center h-full w-full gap-2"> <div class="text-xs flex items-center h-full w-full gap-2">
<component :is="viewIcons[ViewTypes.GALLERY].icon" :class="`text-${viewIcons[ViewTypes.GALLERY].color}`" /> <component :is="viewIcons[ViewTypes.GALLERY].icon" :style="{ color: viewIcons[ViewTypes.GALLERY].color }" />
<div>{{ $t('objects.viewType.gallery') }}</div> <div>{{ $t('objects.viewType.gallery') }}</div>
@ -97,7 +84,7 @@ function onOpenModal(type: ViewTypes, title = '') {
</template> </template>
<div class="text-xs flex items-center h-full w-full gap-2"> <div class="text-xs flex items-center h-full w-full gap-2">
<component :is="viewIcons[ViewTypes.FORM].icon" :class="`text-${viewIcons[ViewTypes.FORM].color}`" /> <component :is="viewIcons[ViewTypes.FORM].icon" :style="{ color: viewIcons[ViewTypes.FORM].color }" />
<div>{{ $t('objects.viewType.form') }}</div> <div>{{ $t('objects.viewType.form') }}</div>
@ -107,27 +94,12 @@ function onOpenModal(type: ViewTypes, title = '') {
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
</div>
<SmartsheetSidebarMenuApiSnippet v-model="showApiSnippet" />
<div class="flex-auto justify-end flex flex-col gap-3 mt-3"> <div class="w-full h-4"></div>
<button
v-if="isUIAllowed('virtualViewsCreateOrEdit')"
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded border transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease !text-xs nc-webhook-btn"
@click="onWebhooks"
>
<mdi-hook />{{ $t('objects.webhooks') }}
</button>
<button
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded border transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease !text-xs"
@click="onApiSnippet"
>
<mdi-xml />Get API Snippet
</button>
</div> </div>
<!--
todo: bring back later
<general-flipping-card class="my-4 lg:my-6 min-h-[100px]" :triggers="['click', { duration: 15000 }]"> <general-flipping-card class="my-4 lg:my-6 min-h-[100px]" :triggers="['click', { duration: 15000 }]">
<template #front> <template #front>
<div class="flex h-full w-full gap-6 flex-col"> <div class="flex h-full w-full gap-6 flex-col">
@ -148,7 +120,7 @@ function onOpenModal(type: ViewTypes, title = '') {
</template> </template>
<template #back> <template #back>
<!-- todo: add project cost --> &lt;!&ndash; todo: add project cost &ndash;&gt;
<a <a
href="https://github.com/sponsors/nocodb" href="https://github.com/sponsors/nocodb"
target="_blank" target="_blank"
@ -159,7 +131,7 @@ function onOpenModal(type: ViewTypes, title = '') {
{{ $t('activity.sponsorUs') }} {{ $t('activity.sponsorUs') }}
</a> </a>
</template> </template>
</general-flipping-card> </general-flipping-card> -->
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" /> <WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
</a-menu> </a-menu>

6
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -170,9 +170,7 @@ function onDeleted() {
</script> </script>
<template> <template>
<h3 class="pt-3 px-3 text-xs text-gray-500 font-semibold">{{ $t('objects.views') }}</h3> <a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu flex-1" :selected-keys="selected">
<a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu" :selected-keys="selected">
<RenameableMenuItem <RenameableMenuItem
v-for="view of views" v-for="view of views"
:id="view.id" :id="view.id"
@ -197,7 +195,7 @@ function onDeleted() {
<style lang="scss"> <style lang="scss">
.nc-views-menu { .nc-views-menu {
@apply flex-1 max-h-[30vh] overflow-y-scroll scrollbar-thin-dull; @apply flex-1 min-h-[100px] overflow-y-scroll scrollbar-thin-dull;
.ghost, .ghost,
.ghost > * { .ghost > * {

2
packages/nc-gui-v2/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -160,7 +160,7 @@ function onStopEdit() {
<component <component
:is="viewIcons[vModel.type].icon" :is="viewIcons[vModel.type].icon"
class="nc-view-icon group-hover:hidden" class="nc-view-icon group-hover:hidden"
:class="`text-${viewIcons[vModel.type].color}`" :style="{ color: viewIcons[vModel?.type]?.color }"
/> />
</div> </div>

39
packages/nc-gui-v2/components/smartsheet/sidebar/index.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk' import type { ViewType, ViewTypes } from 'nocodb-sdk'
import MenuTop from './MenuTop.vue' import MenuTop from './MenuTop.vue'
import MenuBottom from './MenuBottom.vue' import MenuBottom from './MenuBottom.vue'
import Toolbar from './toolbar/index.vue' import Toolbar from './toolbar/index.vue'
@ -81,7 +81,7 @@ function openModal({ type, title = '', copyViewId }: { type: ViewTypes; title: s
} }
/** Handle view creation */ /** Handle view creation */
function onCreate(view: GridType | FormType | KanbanType | GalleryType) { function onCreate(view: ViewType) {
views.value.push(view) views.value.push(view)
activeView.value = view activeView.value = view
router.push({ params: { viewTitle: view.title || '' } }) router.push({ params: { viewTitle: view.title || '' } })
@ -104,35 +104,12 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
class="min-h-[var(--toolbar-height)] max-h-[var(--toolbar-height)]" class="min-h-[var(--toolbar-height)] max-h-[var(--toolbar-height)]"
:class="{ 'flex items-center py-3 px-3 justify-between border-b-1': !isForm }" :class="{ 'flex items-center py-3 px-3 justify-between border-b-1': !isForm }"
/> />
<div v-if="isOpen" class="flex-1 flex flex-col min-h-0">
<Toolbar v-else class="py-3 px-2 max-w-[50px] flex !flex-col-reverse gap-4 items-center mt-[-1px]">
<template #start>
<a-tooltip v-if="isUIAllowed('virtualViewsCreateOrEdit')" placement="left">
<template #title> {{ $t('objects.webhooks') }}</template>
<div class="nc-sidebar-right-item hover:after:bg-gray-300 nc-webhook-icon">
<MdiHook @click.stop />
</div>
</a-tooltip>
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="dot" />
<a-tooltip placement="left">
<template #title> Get API Snippet</template>
<div class="nc-sidebar-right-item group hover:after:bg-yellow-500">
<MdiXml class="group-hover:(!text-white)" @click.stop />
</div>
</a-tooltip>
<div v-if="!isForm" class="dot" />
</template>
</Toolbar>
<div v-if="isOpen" class="flex-1 flex flex-col">
<MenuTop @open-modal="openModal" @deleted="loadViews" @sorted="loadViews" /> <MenuTop @open-modal="openModal" @deleted="loadViews" @sorted="loadViews" />
<a-divider v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="my-2" /> <div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="px-3">
<div class="!my-3 w-full border-b-1 border-dashed" />
</div>
<MenuBottom @open-modal="openModal" /> <MenuBottom @open-modal="openModal" />
</div> </div>
@ -156,8 +133,4 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
:deep(.ant-layout-sider-children) { :deep(.ant-layout-sider-children) {
@apply flex flex-col; @apply flex flex-col;
} }
.dot {
@apply w-[3px] h-[3px] bg-gray-300 rounded-full;
}
</style> </style>

24
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/AddRow.vue

@ -1,24 +0,0 @@
<script setup lang="ts">
import { OpenNewRecordFormHookInj, inject } from '#imports'
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
const isLocked = inject(IsLockedInj)
const openNewRecordFormHook = inject(OpenNewRecordFormHookInj)!
const onClick = () => {
if (!isLocked?.value) openNewRecordFormHook.trigger()
}
</script>
<template>
<a-tooltip :placement="isOpen ? 'bottomRight' : 'left'">
<template #title> {{ $t('activity.addRow') }} </template>
<div
:class="{ 'hover:after:(bg-primary bg-opacity-75) group': !isLocked, 'disabled-ring': isLocked }"
class="nc-sidebar-right-item nc-sidebar-add-row"
>
<MdiPlusOutline :class="{ 'cursor-pointer group-hover:(!text-white)': !isLocked, 'disabled': isLocked }" @click="onClick" />
</div>
</a-tooltip>
</template>

116
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue

@ -1,116 +0,0 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import { computed, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
import MdiAccountIcon from '~icons/mdi/account'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
enum LockType {
Personal = 'personal',
Locked = 'locked',
Collaborative = 'collaborative',
}
const { view, $api } = useSmartsheetStoreOrThrow()
const { $e } = useNuxtApp()
async function changeLockType(type: LockType) {
$e('a:grid:lockmenu', { lockType: type })
if (type === 'personal') {
return message.info('Coming soon')
}
try {
;(view.value as any).lock_type = type
$api.dbView.update(view.value.id as string, {
lock_type: type,
})
message.success(`Successfully Switched to ${type} view`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const Icon = computed(() => {
switch ((view.value as any)?.lock_type) {
case LockType.Personal:
return MdiAccountIcon
case LockType.Locked:
return MdiLockOutlineIcon
case LockType.Collaborative:
default:
return MdiAccountGroupIcon
}
})
</script>
<template>
<a-dropdown max-width="350" :trigger="['click']">
<div class="nc-sidebar-right-item hover:after:bg-indigo-500 group nc-sidebar-lock-menu">
<Icon class="cursor-pointer group-hover:(!text-white)" />
</div>
<template #overlay>
<div class="min-w-[350px] max-w-[500px] shadow bg-white">
<div>
<div class="nc-menu-item" @click="changeLockType(LockType.Collaborative)">
<div>
<MdiCheck v-if="!view?.lock_type || view?.lock_type === LockType.Collaborative" />
<span v-else />
<div>
<MdiAccountGroupIcon />
Collaborative view
<div class="nc-subtitle">Collaborators with edit permissions or higher can change the view configuration.</div>
</div>
</div>
</div>
<div class="nc-menu-item" @click="changeLockType(LockType.Locked)">
<div>
<MdiCheck v-if="view.lock_type === LockType.Locked" />
<span v-else />
<div>
<MdiLockOutlineIcon />
Locked View
<div class="nc-subtitle">No one can edit the view configuration until it is unlocked.</div>
</div>
</div>
</div>
<div class="nc-menu-item" @click="changeLockType(LockType.Personal)">
<div>
<MdiCheck v-if="view.lock_type === LockType.Personal" />
<span v-else />
<div>
<MdiAccountIcon />
Personal view
<div class="nc-subtitle">
Only you can edit the view configuration. Other collaborators personal views are hidden by default.
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</a-dropdown>
</template>
<style scoped>
.nc-menu-item > div {
@apply grid grid-cols-[30px,auto] gap-2 p-2 items-center;
}
.nc-menu-item > div > svg {
align-self: center;
}
.nc-menu-option > :first-child {
@apply self-center;
}
.nc-subtitle {
@apply font-size-sm font-weight-light;
}
</style>

17
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue

@ -4,15 +4,12 @@ const { isOpen, toggle } = useSidebar({ storageKey: 'nc-right-sidebar' })
</script> </script>
<template> <template>
<a-tooltip :placement="isOpen ? 'bottomRight' : 'left'" :mouse-enter-delay="0.8"> <div :class="{ 'nc-active-btn': isOpen }">
<template #title> Toggle sidebar</template> <a-button size="small" @click="toggle(!isOpen)">
<div class="flex items-center gap-1 text-xs" :class="{ 'text-gray-500': !isOpen }">
<div class="nc-sidebar-right-item hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row"> <MdiMenu class="!text-xs" />
<MdiChevronDoubleLeft Views
class="cursor-pointer group-hover:(!text-white) transform transition-transform" </div>
:class="{ 'rotate-180': isOpen }" </a-button>
@click="toggle(!isOpen)"
/>
</div> </div>
</a-tooltip>
</template> </template>

26
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/index.vue

@ -1,28 +1,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import AddRow from './AddRow.vue'
import LockMenu from './LockMenu.vue'
import Reload from './Reload.vue'
import ExportCache from './ExportCache.vue' import ExportCache from './ExportCache.vue'
import DeleteCache from './DeleteCache.vue' import DeleteCache from './DeleteCache.vue'
import DebugMeta from './DebugMeta.vue' import DebugMeta from './DebugMeta.vue'
import ToggleDrawer from './ToggleDrawer.vue' import ToggleDrawer from './ToggleDrawer.vue'
import { IsFormInj } from '#imports' import { IsFormInj } from '#imports'
const { isUIAllowed } = useUIPermission()
const isForm = inject(IsFormInj) const isForm = inject(IsFormInj)
const debug = $ref(false) const debug = $ref(false)
const clickCount = $ref(0) const clickCount = $ref(0)
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
</script> </script>
<template> <template>
<div <div
v-if="!isForm" v-if="!isForm"
class="flex gap-2" class="flex gap-2 justify-start"
@click=" @click="
() => { () => {
clickCount = clickCount + 1 clickCount = clickCount + 1
@ -31,7 +24,8 @@ const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
" "
> >
<slot name="start" /> <slot name="start" />
<ToggleDrawer />
<span></span>
<template v-if="debug"> <template v-if="debug">
<ExportCache /> <ExportCache />
@ -46,20 +40,6 @@ const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
<div class="dot" /> <div class="dot" />
</template> </template>
<LockMenu v-if="isUIAllowed('view-type')" @click.stop />
<div v-if="isUIAllowed('view-type')" class="dot" />
<Reload @click.stop />
<div class="dot" />
<AddRow v-if="isUIAllowed('xcDatatableEditable')" @click.stop />
<div :class="{ 'w-[calc(100%_+_16px)] h-[1px] bg-gray-200 mt-1 -ml-1': !isOpen, 'dot': isOpen }" />
<ToggleDrawer />
<slot name="end" /> <slot name="end" />
</div> </div>
<div v-else> <div v-else>

11
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -76,7 +76,7 @@ watch(isLocked, (nextValue) => (treeViewIsLockedInj.value = nextValue), { immedi
<template v-if="meta"> <template v-if="meta">
<div class="flex flex-1 min-h-0"> <div class="flex flex-1 min-h-0">
<div v-if="activeView" class="h-full flex-1 min-w-0 min-h-0"> <div v-if="activeView" class="h-full flex-1 min-w-0 min-h-0 bg-gray-50">
<SmartsheetGrid v-if="isGrid" :ref="el" /> <SmartsheetGrid v-if="isGrid" :ref="el" />
<SmartsheetGallery v-else-if="isGallery" /> <SmartsheetGallery v-else-if="isGallery" />
@ -86,7 +86,12 @@ watch(isLocked, (nextValue) => (treeViewIsLockedInj.value = nextValue), { immedi
</div> </div>
</template> </template>
</div> </div>
<SmartsheetSidebar v-if="meta" class="nc-right-sidebar" />
<SmartsheetSidebar v-if="meta" />
</div> </div>
</template> </template>
<style scoped>
:deep(.nc-right-sidebar.ant-layout-sider-collapsed) {
@apply !w-0 !max-w-0 !min-w-0 overflow-x-hidden;
}
</style>

2
packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue

@ -80,7 +80,7 @@ const unlinkRef = async (rec: Record<string, any>) => {
</div> </div>
<div <div
v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')"
class="flex-1 flex justify-end gap-1 min-h-[30px] items-center" class="flex justify-end gap-1 min-h-[30px] items-center"
> >
<component <component
:is="addIcon" :is="addIcon"

5
packages/nc-gui-v2/components/virtual-cell/HasMany.vue

@ -95,10 +95,7 @@ const unlinkRef = async (rec: Record<string, any>) => {
</span> </span>
</template> </template>
</div> </div>
<div <div v-if="!isLocked && isUIAllowed('xcDatatableEditable')" class="flex justify-end gap-1 min-h-[30px] items-center">
v-if="!isLocked && isUIAllowed('xcDatatableEditable')"
class="flex-1 flex justify-end gap-1 min-h-[30px] items-center"
>
<MdiArrowExpand <MdiArrowExpand
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"
@click="childListDlg = true" @click="childListDlg = true"

2
packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue

@ -94,7 +94,7 @@ const unlinkRef = async (rec: Record<string, any>) => {
</template> </template>
</div> </div>
<div v-if="!isLocked && isUIAllowed('xcDatatableEditable')" class="flex-1 flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isLocked && isUIAllowed('xcDatatableEditable')" class="flex justify-end gap-1 min-h-[30px] items-center">
<MdiArrowExpand <MdiArrowExpand
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"
@click="childListDlg = true" @click="childListDlg = true"

3
packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue

@ -44,7 +44,7 @@ export default {
<template> <template>
<div <div
class="group py-1 px-2 mr-1 my-1 flex items-center bg-blue-100/60 hover:bg-blue-100/40 rounded-[2px]" class="chip group py-1 px-2 mr-1 my-1 flex items-center bg-blue-100/60 hover:bg-blue-100/40 rounded-[2px]"
:class="{ active }" :class="{ active }"
@click="expandedFormDlg = true" @click="expandedFormDlg = true"
> >
@ -74,6 +74,7 @@ export default {
.name { .name {
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap;
} }
} }
</style> </style>

14
packages/nc-gui-v2/components/virtual-cell/components/ListChildItems.vue

@ -81,9 +81,9 @@ const expandedFormRow = ref()
</script> </script>
<template> <template>
<component :is="container" v-model:visible="vModel" :footer="null" title="Child list"> <component :is="container" v-model:visible="vModel" :footer="null" title="Child list" :body-style="{ padding: 0 }">
<div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col"> <div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col py-6">
<div class="flex mb-4 items-center gap-2"> <div class="flex mb-4 items-center gap-2 px-12">
<div class="flex-1" /> <div class="flex-1" />
<MdiReload v-if="!isForm" class="cursor-pointer text-gray-500" @click="loadChildrenList" /> <MdiReload v-if="!isForm" class="cursor-pointer text-gray-500" @click="loadChildrenList" />
@ -96,11 +96,11 @@ const expandedFormRow = ref()
</a-button> </a-button>
</div> </div>
<template v-if="(isNew && state?.[column?.title]?.length) || childrenList?.pageInfo?.totalRows"> <template v-if="(isNew && state?.[column?.title]?.length) || childrenList?.pageInfo?.totalRows">
<div class="flex-1 overflow-auto min-h-0"> <div class="flex-1 overflow-auto min-h-0 scrollbar-thin-dull px-12">
<a-card <a-card
v-for="(row, i) of childrenList?.list ?? state?.[column?.title] ?? []" v-for="(row, i) of childrenList?.list ?? state?.[column?.title] ?? []"
:key="i" :key="i"
class="m-2 hover:(!bg-gray-200/50 shadow-md)" class="!my-4 hover:(!bg-gray-200/50 shadow-md)"
@click=" @click="
() => { () => {
expandedFormRow = row expandedFormRow = row
@ -113,7 +113,6 @@ const expandedFormRow = ref()
{{ row[relatedTablePrimaryValueProp] }} {{ row[relatedTablePrimaryValueProp] }}
<span class="text-gray-400 text-[11px] ml-1">(Primary key : {{ getRelatedTableRowId(row) }})</span> <span class="text-gray-400 text-[11px] ml-1">(Primary key : {{ getRelatedTableRowId(row) }})</span>
</div> </div>
<div class="flex-1"></div>
<div v-if="!readonly" class="flex gap-2"> <div v-if="!readonly" class="flex gap-2">
<MdiLinkVariantRemove <MdiLinkVariantRemove
class="text-xs text-grey hover:(!text-red-500) cursor-pointer" class="text-xs text-grey hover:(!text-red-500) cursor-pointer"
@ -128,6 +127,8 @@ const expandedFormRow = ref()
</div> </div>
</a-card> </a-card>
</div> </div>
<div class="flex justify-center mt-6">
<a-pagination <a-pagination
v-if="!isNew && childrenList?.pageInfo" v-if="!isNew && childrenList?.pageInfo"
v-model:current="childrenListPagination.page" v-model:current="childrenListPagination.page"
@ -137,6 +138,7 @@ const expandedFormRow = ref()
:total="childrenList.pageInfo.totalRows" :total="childrenList.pageInfo.totalRows"
show-less-items show-less-items
/> />
</div>
</template> </template>
<a-empty <a-empty
v-else v-else

14
packages/nc-gui-v2/components/virtual-cell/components/ListItems.vue

@ -97,9 +97,9 @@ const newRowState = computed(() => {
</script> </script>
<template> <template>
<a-modal v-model:visible="vModel" :footer="null" title="Link Record"> <a-modal v-model:visible="vModel" :footer="null" title="Link Record" :body-style="{ padding: 0 }">
<div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col"> <div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col py-6">
<div class="flex mb-4 items-center gap-2"> <div class="flex mb-4 items-center gap-2 px-12">
<a-input <a-input
v-model:value="childrenExcludedListPagination.query" v-model:value="childrenExcludedListPagination.query"
placeholder="Filter query" placeholder="Filter query"
@ -111,11 +111,11 @@ const newRowState = computed(() => {
<a-button v-if="!isPublic" type="primary" size="small" @click="expandedFormDlg = true">Add new record</a-button> <a-button v-if="!isPublic" type="primary" size="small" @click="expandedFormDlg = true">Add new record</a-button>
</div> </div>
<template v-if="childrenExcludedList?.pageInfo?.totalRows"> <template v-if="childrenExcludedList?.pageInfo?.totalRows">
<div class="flex-1 overflow-auto min-h-0"> <div class="flex-1 overflow-auto min-h-0 scrollbar-thin-dull px-12">
<a-card <a-card
v-for="(refRow, i) in childrenExcludedList?.list ?? []" v-for="(refRow, i) in childrenExcludedList?.list ?? []"
:key="i" :key="i"
class="m-2 cursor-pointer hover:(!bg-gray-200/50 shadow-md) group" class="!my-4 cursor-pointer hover:(!bg-gray-200/50 shadow-md) group"
@click="linkRow(refRow)" @click="linkRow(refRow)"
> >
{{ refRow[relatedTablePrimaryValueProp] {{ refRow[relatedTablePrimaryValueProp]
@ -124,15 +124,17 @@ const newRowState = computed(() => {
> >
</a-card> </a-card>
</div> </div>
<div class="flex justify-center mt-6">
<a-pagination <a-pagination
v-if="childrenExcludedList?.pageInfo" v-if="childrenExcludedList?.pageInfo"
v-model:current="childrenExcludedListPagination.page" v-model:current="childrenExcludedListPagination.page"
v-model:page-size="childrenExcludedListPagination.size" v-model:page-size="childrenExcludedListPagination.size"
class="mt-2 mx-auto !text-xs" class="mt-2 !text-xs"
size="small" size="small"
:total="childrenExcludedList.pageInfo.totalRows" :total="childrenExcludedList.pageInfo.totalRows"
show-less-items show-less-items
/> />
</div>
</template> </template>
<a-empty v-else class="my-10" :image="Empty.PRESENTED_IMAGE_SIMPLE" /> <a-empty v-else class="my-10" :image="Empty.PRESENTED_IMAGE_SIMPLE" />

2
packages/nc-gui-v2/composables/useTheme/index.ts

@ -1,7 +1,7 @@
import { ConfigProvider } from 'ant-design-vue' import { ConfigProvider } from 'ant-design-vue'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider' import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import { NOCO, hexToRGB, themeV2Colors, toRefs, useCssVar, useInjectionState } from '#imports' import { NOCO, hexToRGB, themeV2Colors, useCssVar, useInjectionState } from '#imports'
interface ThemeConfig extends AntTheme { interface ThemeConfig extends AntTheme {
primaryColor: string primaryColor: string

6
packages/nc-gui-v2/lib/enums.ts

@ -54,3 +54,9 @@ export enum NavigateDir {
NEXT, NEXT,
PREV, PREV,
} }
export enum LockType {
Personal = 'personal',
Locked = 'locked',
Collaborative = 'collaborative',
}

2
packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue

@ -203,7 +203,7 @@ const onMenuClose = (visible: boolean) => {
</a-tooltip> </a-tooltip>
<div v-else class="text-lg font-semibold truncate">{{ project.title }}</div> <div v-else class="text-lg font-semibold truncate">{{ project.title }}</div>
<MdiChevronDown class="min-w-[28.5px] group-hover:text-accent text-2xl" /> <MdiChevronDown class="min-w-[17px] group-hover:text-accent text-md" />
</template> </template>
<template v-else> <template v-else>

4
packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue

@ -56,10 +56,10 @@ const { isOpen, toggle } = useSidebar()
<span class="flex-1" /> <span class="flex-1" />
<div class="flex justify-center self-center mr-2 min-w-[115px]"> <div class="flex justify-center self-center mr-2 min-w-[115px]">
<div v-show="isLoading" class="flex items-center gap-2 ml-3 text-white"> <div v-show="isLoading" class="flex items-center gap-2 ml-3 text-gray-200">
{{ $t('general.loading') }} {{ $t('general.loading') }}
<MdiReload :class="{ 'animate-infinite animate-spin': isLoading }" /> <MdiLoading :class="{ 'animate-infinite animate-spin': isLoading }" />
</div> </div>
</div> </div>
</div> </div>

5
packages/nc-gui-v2/utils/viewUtils.ts

@ -1,4 +1,5 @@
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { themeV2Colors } from '~/utils'
import MdiGridIcon from '~icons/mdi/grid-large' import MdiGridIcon from '~icons/mdi/grid-large'
import MdiFormIcon from '~icons/mdi/form-select' import MdiFormIcon from '~icons/mdi/form-select'
@ -8,8 +9,8 @@ import MdiKanbanIcon from '~icons/mdi/tablet-dashboard'
import MdiEyeIcon from '~icons/mdi/eye-circle-outline' import MdiEyeIcon from '~icons/mdi/eye-circle-outline'
export const viewIcons = { export const viewIcons = {
[ViewTypes.GRID]: { icon: MdiGridIcon, color: 'blue' }, [ViewTypes.GRID]: { icon: MdiGridIcon, color: '#8f96f2' },
[ViewTypes.FORM]: { icon: MdiFormIcon, color: 'pink' }, [ViewTypes.FORM]: { icon: MdiFormIcon, color: themeV2Colors.pink['500'] },
calendar: { icon: MdiCalendarIcon, color: 'purple' }, calendar: { icon: MdiCalendarIcon, color: 'purple' },
[ViewTypes.GALLERY]: { icon: MdiGalleryIcon, color: 'orange' }, [ViewTypes.GALLERY]: { icon: MdiGalleryIcon, color: 'orange' },
[ViewTypes.KANBAN]: { icon: MdiKanbanIcon, color: 'green' }, [ViewTypes.KANBAN]: { icon: MdiKanbanIcon, color: 'green' },

1
packages/nc-gui/nuxt.config.js

@ -224,6 +224,7 @@ export default {
config.output.publicPath = './_nuxt/' config.output.publicPath = './_nuxt/'
} }
return config return config
} }
}, },

Loading…
Cancel
Save