Browse Source

Merge branch 'develop' into fix/gui-v2-shared-base-issues

pull/3234/head
Wing-Kam Wong 2 years ago
parent
commit
b4f50a5616
  1. 23
      packages/nc-gui-v2/assets/style-v2.scss
  2. 1
      packages/nc-gui-v2/components.d.ts
  3. 182
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  4. 48
      packages/nc-gui-v2/components/dlg/TableCreate.vue
  5. 10
      packages/nc-gui-v2/components/general/MiniSidebar.vue
  6. 10
      packages/nc-gui-v2/components/general/PreviewAs.vue
  7. 2
      packages/nc-gui-v2/components/general/ShareBaseButton.vue
  8. 55
      packages/nc-gui-v2/components/general/language/Menu.vue
  9. 30
      packages/nc-gui-v2/components/general/language/index.vue
  10. 2
      packages/nc-gui-v2/components/smartsheet/expanded-form/Comments.vue
  11. 2
      packages/nc-gui-v2/components/smartsheet/expanded-form/Header.vue
  12. 2
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue
  13. 2
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  14. 7
      packages/nc-gui-v2/composables/useTable.ts
  15. 22
      packages/nc-gui-v2/layouts/base.vue
  16. 10
      packages/nc-gui-v2/layouts/shared-view.vue
  17. 1
      packages/nc-gui-v2/lib/constants.ts
  18. 106
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  19. 19
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue
  20. 44
      packages/nocodb/src/lib/meta/api/columnApis.ts
  21. 8
      packages/nocodb/src/lib/models/Column.ts
  22. 2
      packages/nocodb/src/lib/models/Model.ts

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

@ -213,11 +213,25 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
}
}
.ant-tabs-dropdown-menu-title-content{
@apply flex items-center;
.ant-dropdown-menu-submenu {
@apply !py-0;
.ant-dropdown-menu, .ant-menu {
@apply m-0 p-0;
}
.ant-menu-item {
@apply !m-0 !px-2;
}
}
.ant-dropdown-menu-submenu-popup {
@apply scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !shadow !rounded;
}
.ant-tabs-dropdown-menu-title-content {
@apply flex items-center;
}
.ant-dropdown-menu-item-group-list {
@apply !mx-0;
@ -231,10 +245,11 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
@apply m-0;
}
.ant-dropdown-menu-item {
.ant-dropdown-menu-item, .ant-menu-item {
@apply !py-0 active:(ring ring-pink-500);
}
.ant-dropdown-menu-title-conten{
.ant-dropdown-menu-title-content,
.ant-menu-title-content {
@apply !py-0;
}

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

@ -15,6 +15,7 @@ declare module '@vue/runtime-core' {
ACardMeta: typeof import('ant-design-vue/es')['CardMeta']
ACarousel: typeof import('ant-design-vue/es')['Carousel']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
ACol: typeof import('ant-design-vue/es')['Col']
ACollapse: typeof import('ant-design-vue/es')['Collapse']
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']

182
packages/nc-gui-v2/components/dashboard/TreeView.vue

@ -2,11 +2,23 @@
import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs'
import { Empty } from 'ant-design-vue'
import { useNuxtApp } from '#app'
import { computed, useProject, useTable, useTabs, useUIPermission, watchEffect } from '#imports'
import {
computed,
inject,
reactive,
ref,
useDialog,
useNuxtApp,
useProject,
useTable,
useTabs,
useUIPermission,
watchEffect,
} from '#imports'
import DlgAirtableImport from '~/components/dlg/AirtableImport.vue'
import DlgQuickImport from '~/components/dlg/QuickImport.vue'
import DlgTableCreate from '~/components/dlg/TableCreate.vue'
import DlgTableRename from '~/components/dlg/TableRename.vue'
import { TabType } from '~/composables'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large'
@ -27,16 +39,25 @@ const { isUIAllowed } = useUIPermission()
const isLocked = inject('TreeViewIsLockedInj')
const tablesById = $computed<Record<string, TableType>>(() =>
tables?.value?.reduce((acc: Record<string, TableType>, table: TableType) => {
acc[table.id as string] = table
let key = $ref(0)
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
}, {}),
)
let key = $ref(0)
const menuRef = $ref<HTMLLIElement>()
const filteredTables = $computed(() =>
tables.value?.filter((table) => !filterQuery || table.title.toLowerCase().includes(filterQuery.toLowerCase())),
)
let sortable: Sortable
@ -104,37 +125,44 @@ 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 setMenuContext = (type: 'table' | 'main', value?: any) => {
contextMenuTarget.type = type
contextMenuTarget.value = value
$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 () => {
$e('a:table:refresh:navdraw')
await loadTables()
}
const addTableTab = (table: TableType) => {
$e('a:table:open')
addTab({ title: table.title, id: table.id, type: table.type as any })
}
const activeTable = computed(() => {
return [TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.title : null
})
function openRenameTableDialog(table: TableType, rightClick = false) {
$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) {
$e(`a:actions:import-${type}`)
@ -195,30 +223,29 @@ function openTableCreateDialog() {
<div
class="pt-2 pl-2 pb-2 flex-1 overflow-y-auto flex flex-column scrollbar-thin-dull"
: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">
{{ $t('objects.tables') }}
<template v-if="tables?.length"> ({{ tables.length }}) </template>
</span>
</div>
<div style="direction: ltr" class="flex-1">
<div class="flex-1">
<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"
@click="openTableCreateDialog"
>
<MdiPlus />
<span class="text-gray-500 group-hover:(text-primary/100) flex-1">{{ $t('tooltip.addTable') }}</span>
<a-dropdown v-if="!isSharedBase" :trigger="['click']" @click.stop>
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100" />
<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
v-if="isUIAllowed('airtableImport')"
@ -321,23 +348,18 @@ function openTableCreateDialog() {
<MdiMenuIcon class="transition-opacity opacity-0 group-hover:opacity-100" />
<template #overlay>
<a-menu class="cursor-pointer">
<a-menu-item
v-if="isUIAllowed('table-rename')"
v-t="['c:table:rename']"
class="!text-xs"
@click="showRenameTableDlg(table)"
><div>{{ $t('general.rename') }}</div></a-menu-item
>
<a-menu-item
v-if="isUIAllowed('table-delete')"
v-t="['c:table:delete']"
class="!text-xs"
@click="deleteTable(table)"
>
{{ $t('general.delete') }}</a-menu-item
>
<a-menu class="!py-0 rounded text-sm">
<a-menu-item v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(table)">
<div class="nc-project-menu-item">
{{ $t('general.rename') }}
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('table-delete')" @click="() => $e('c:table:delete') && deleteTable(table)">
<div class="nc-project-menu-item">
{{ $t('general.delete') }}
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
@ -350,35 +372,38 @@ function openTableCreateDialog() {
<div class="flex flex-col align-center">
<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>
</a-card>
</div>
</div>
<template v-if="!isLocked && !isSharedBase" #overlay>
<a-menu class="cursor-pointer">
<a-menu class="!py-0 rounded text-sm">
<template v-if="contextMenuTarget.type === 'table'">
<a-menu-item
v-if="isUIAllowed('table-rename')"
v-t="['c:table:rename']"
class="!text-xs"
@click="showRenameTableDlg(contextMenuTarget.value)"
>
{{ $t('general.rename') }}
<a-menu-item v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(contextMenuTarget.value)">
<div class="nc-project-menu-item">
{{ $t('general.rename') }}
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('table-delete')"
v-t="['c:table:delete']"
class="!text-xs"
@click="deleteTable(contextMenuTarget.value)"
@click="() => $e('c:table:delete') && deleteTable(contextMenuTarget.value)"
>
{{ $t('general.delete') }}
<div class="nc-project-menu-item">
{{ $t('general.delete') }}
</div>
</a-menu-item>
</template>
<template v-else>
<a-menu-item v-t="['c:table:reload']" class="!text-xs" @click="reloadTables">
{{ $t('general.reload') }}
<a-menu-item @click="reloadTables">
<div class="nc-project-menu-item">
{{ $t('general.reload') }}
</div>
</a-menu-item>
</template>
</a-menu>
@ -388,15 +413,8 @@ function openTableCreateDialog() {
<a-divider class="mt-0 mb-0" />
<div class="items-center flex justify-center p-2">
<!--
Todo : move the component
<GithubStarButton />
-->
<GeneralShareBaseButton class="!mr-0" />
</div>
<DlgTableRename v-if="renameTableMeta" v-model="renameTableDlg" :table-meta="renameTableMeta" />
</div>
</template>
@ -472,4 +490,28 @@ function openTableCreateDialog() {
@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>

48
packages/nc-gui-v2/components/dlg/TableCreate.vue

@ -45,6 +45,11 @@ const validators = computed(() => {
})
const { validateInfos } = useForm(table, validators)
const systemColumnsCheckboxInfo = SYSTEM_COLUMNS.map((c, index) => ({
value: c,
disabled: index === 0,
}))
onMounted(() => {
generateUniqueTitle()
@ -78,8 +83,8 @@ onMounted(() => {
/>
</a-form-item>
<div class="flex justify-end">
<div class="pointer" @click="isAdvanceOptVisible = !isAdvanceOptVisible">
<div class="flex justify-end items-center">
<div class="pointer flex flex-row items-center gap-x-1" @click="isAdvanceOptVisible = !isAdvanceOptVisible">
{{ isAdvanceOptVisible ? 'Hide' : 'Show' }} more
<MdiMinusCircleOutline v-if="isAdvanceOptVisible" class="text-gray-500" />
@ -95,32 +100,29 @@ onMounted(() => {
</a-form-item>
<div>
<div class="mb-5">
<div class="mb-1">
<!-- Add Default Columns -->
{{ $t('msg.info.addDefaultColumns') }}
</div>
<a-row>
<a-col :span="6">
<a-tooltip placement="top">
<template #title>
<span>ID column is required, you can rename this later if required.</span>
</template>
<a-checkbox v-model:checked="table.columns.id" disabled>ID</a-checkbox>
</a-tooltip>
</a-col>
<a-col :span="6">
<a-checkbox v-model:checked="table.columns.title"> title </a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox v-model:checked="table.columns.created_at"> created_at </a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox v-model:checked="table.columns.updated_at"> updated_at </a-checkbox>
</a-col>
<a-checkbox-group
v-model:value="table.columns"
:options="systemColumnsCheckboxInfo"
class="!flex flex-row justify-between w-full"
>
<template #label="{ value }">
<a-tooltip v-if="value === 'id'" placement="top" class="!flex">
<template #title>
<span>ID column is required, you can rename this later if required.</span>
</template>
ID
</a-tooltip>
<div v-else class="flex">
{{ value }}
</div>
</template>
</a-checkbox-group>
</a-row>
</div>
</div>

10
packages/nc-gui-v2/components/general/MiniSidebar.vue

@ -34,7 +34,7 @@ const logout = () => {
</div>
<template v-if="signedIn" #overlay>
<a-menu class="ml-2 !py-0 min-w-32 leading-8 !rounded">
<a-menu class="ml-2 !py-0 min-w-32 leading-8 !rounded nc-menu-account">
<a-menu-item-group title="User Settings">
<a-menu-item key="email" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user">
@ -129,12 +129,4 @@ const logout = () => {
}
}
}
:deep(.ant-dropdown-menu-item-group-title) {
@apply border-b-1;
}
:deep(.ant-dropdown-menu-item-group-list) {
@apply m-0;
}
</style>

10
packages/nc-gui-v2/components/general/PreviewAs.vue

@ -77,11 +77,13 @@ watch(previewAs, () => window.location.reload())
<template v-else>
<template v-for="role of roleList" :key="role.title">
<a-menu-item :class="`pointer nc-preview-${role.title}`" @click="previewAs = role.title">
<a-menu-item @click="previewAs = role.title">
<div class="nc-project-menu-item group">
<component :is="roleIcon[role.title]" class="group-hover:text-pink-500" />
<span class="capitalize" :class="{ 'x-active--text': role.title === previewAs }">{{ role.title }}</span>
<span class="capitalize" :class="{ 'x-active--text': role.title === previewAs }">
{{ role.title }}
</span>
</div>
</a-menu-item>
</template>
@ -91,7 +93,9 @@ watch(previewAs, () => window.location.reload())
<div class="nc-project-menu-item group">
<MdiClose class="group-hover:text-pink-500" />
<!-- Reset Preview -->
<span class="text-capitalize text-xs whitespace-nowrap">{{ $t('activity.resetReview') }}</span>
<span class="text-capitalize text-xs whitespace-nowrap">
{{ $t('activity.resetReview') }}
</span>
</div>
</a-menu-item>
</template>

2
packages/nc-gui-v2/components/general/ShareBaseButton.vue

@ -24,7 +24,7 @@ const { isUIAllowed } = useUIPermission()
@click="showUserModal = true"
>
<div class="flex items-center space-x-1">
<mdi-account-supervisor-outline class="mr-1" />
<mdi-account-supervisor-outline class="mr-1 nc-share-base" />
<div>{{ $t('activity.share') }}</div>
</div>
</a-button>

55
packages/nc-gui-v2/components/general/language/Menu.vue

@ -1,52 +1,61 @@
<script lang="ts" setup>
import { Language } from '~/lib'
import { onMounted, useGlobal, useI18n, useNuxtApp } from '#imports'
const { $e } = useNuxtApp()
const { lang: currentLang } = useGlobal()
const { availableLocales = ['en'], locale } = useI18n()
const languages = $computed(() => availableLocales.sort())
const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
function applyDirection() {
const targetDirection = isRtlLang ? 'rtl' : 'ltr'
const oppositeDirection = targetDirection === 'ltr' ? 'rtl' : 'ltr'
document.body.classList.remove(oppositeDirection)
document.body.classList.add(targetDirection)
document.body.style.direction = targetDirection
}
function changeLanguage(lang: string) {
currentLang.value = lang
locale.value = lang
applyDirection()
$e('c:navbar:lang', { lang })
}
onMounted(() => {
applyDirection()
})
</script>
<template>
<a-menu class="scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0 rounded">
<a-menu-item
v-for="lang of languages"
:key="lang"
:class="lang === locale ? '!bg-primary/10 text-primary' : ''"
class="group"
:value="lang"
@click="changeLanguage(lang)"
<a-menu-item
v-for="lang of languages"
:key="lang"
:class="lang === locale ? '!bg-primary/10 text-primary' : ''"
class="group"
:value="lang"
@click="changeLanguage(lang)"
>
<div :class="lang === locale ? '!font-semibold !text-primary' : ''" class="nc-project-menu-item capitalize">
{{ Language[lang] || lang }}
</div>
</a-menu-item>
<a-menu-item class="mt-1">
<a
href="https://docs.nocodb.com/engineering/translation/#how-to-contribute--for-community-members"
target="_blank"
class="caption py-2 text-primary underline hover:opacity-75"
>
<div :class="lang === locale ? '!font-semibold !text-primary' : ''" class="nc-project-menu-item capitalize">
{{ Language[lang] || lang }}
</div>
</a-menu-item>
<a-menu-item class="mt-1">
<a
href="https://docs.nocodb.com/engineering/translation/#how-to-contribute--for-community-members"
target="_blank"
class="caption py-2 text-primary underline hover:opacity-75"
>
{{ $t('activity.translate') }}
</a>
</a-menu-item>
</a-menu>
{{ $t('activity.translate') }}
</a>
</a-menu-item>
</template>

30
packages/nc-gui-v2/components/general/language/index.vue

@ -1,33 +1,11 @@
<script lang="ts" setup>
const { subMenu } = defineProps<{ subMenu?: boolean }>()
</script>
<template>
<GeneralLanguageMenu v-if="subMenu" />
<a-dropdown v-else class="select-none color-transition" :trigger="['click']">
<a-dropdown class="select-none color-transition" :trigger="['click']">
<MaterialSymbolsTranslate v-bind="$attrs" class="md:text-xl cursor-pointer nc-menu-translate" />
<template #overlay>
<GeneralLanguageMenu />
<a-menu class="scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0 rounded">
<GeneralLanguageMenu />
</a-menu>
</template>
</a-dropdown>
</template>
<style scoped>
: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);
}
</style>

2
packages/nc-gui-v2/components/smartsheet/expanded-form/Comments.vue

@ -62,7 +62,7 @@ watch(
<div class="flex-shrink-1 mt-2 d-flex">
<a-input
v-model:value="comment"
class="!text-xs"
class="!text-xs nc-comment-box"
ghost
:class="{ focus: showborder }"
@focusin="showborder = true"

2
packages/nc-gui-v2/components/smartsheet/expanded-form/Header.vue

@ -58,7 +58,7 @@ const iconColor = '#1890ff'
<component
:is="drawerToggleIcon"
v-if="isUIAllowed('rowComments')"
class="cursor-pointer select-none"
class="cursor-pointer select-none nc-toggle-comments"
@click="commentsDrawer = !commentsDrawer"
/>

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

@ -114,7 +114,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-auto justify-end flex flex-col gap-3 mt-3">
<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"
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') }}

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

@ -110,7 +110,7 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
<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">
<div class="nc-sidebar-right-item hover:after:bg-gray-300 nc-webhook-icon">
<MdiHook @click.stop />
</div>
</a-tooltip>

7
packages/nc-gui-v2/composables/useTable.ts

@ -1,16 +1,15 @@
import { Modal, message } from 'ant-design-vue'
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { useProject } from './useProject'
import { TabType } from '~/composables/useTabs'
import { extractSdkResponseErrorMsg } from '~/utils'
import { useNuxtApp } from '#app'
import { TabType } from '~/composables/useTabs'
import { SYSTEM_COLUMNS, extractSdkResponseErrorMsg, useProject } from '#imports'
export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
const table = reactive<{ title: string; table_name: string; columns: string[] }>({
title: '',
table_name: '',
columns: ['id', 'title', 'created_at', 'updated_at'],
columns: SYSTEM_COLUMNS,
})
const { $e, $api } = useNuxtApp()

22
packages/nc-gui-v2/layouts/base.vue

@ -60,7 +60,7 @@ const logout = () => {
<template v-if="signedIn && !isSharedBase">
<a-dropdown :trigger="['click']">
<MdiDotsVertical class="md:text-xl cursor-pointer hover:text-pink-500" @click.prevent />
<MdiDotsVertical class="md:text-xl cursor-pointer hover:text-pink-500 nc-menu-accounts" @click.prevent />
<template #overlay>
<a-menu class="!py-0 dark:(!bg-gray-800) leading-8 !rounded">
@ -108,26 +108,6 @@ const logout = () => {
</template>
<style lang="scss" scoped>
: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);
}
.nc-lang-btn {
@apply color-transition flex items-center justify-center fixed bottom-10 right-10 z-99 w-12 h-12 rounded-full shadow-md shadow-gray-500 p-2 !bg-primary text-white active:(ring ring-pink-500) hover:(ring ring-pink-500);

10
packages/nc-gui-v2/layouts/shared-view.vue

@ -40,13 +40,3 @@ export default {
</a-layout>
</a-layout>
</template>
<style lang="scss" scoped>
:deep(.ant-dropdown-menu-item-group-title) {
@apply border-b-1;
}
:deep(.ant-dropdown-menu-item-group-list) {
@apply m-0;
}
</style>

1
packages/nc-gui-v2/lib/constants.ts

@ -1,2 +1,3 @@
export const NOCO = 'noco'
export const USER_PROJECT_ROLES = 'user_project_roles'
export const SYSTEM_COLUMNS = ['id', 'title', 'created_at', 'updated_at']

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

@ -196,7 +196,7 @@ definePageMeta({
<a-menu-item key="copy">
<div class="nc-project-menu-item group" @click.stop="copyProjectInfo">
<MdiContentCopy class="group-hover:text-pink-500 nc-copy-project-info" />
<MdiContentCopy class="group-hover:text-pink-500" />
Copy Project Info
</div>
</a-menu-item>
@ -210,14 +210,14 @@ definePageMeta({
class="nc-project-menu-item group"
@click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.ncSiteUrl)"
>
<MdiApi class="group-hover:text-pink-500 nc-swagger-api-docs" />
<MdiApi class="group-hover:text-pink-500" />
Swagger: Rest APIs
</div>
</a-menu-item>
<a-menu-item key="copy">
<div v-t="['a:navbar:user:copy-auth-token']" class="nc-project-menu-item group" @click.stop="copyAuthToken">
<MdiScriptTextKeyOutline class="group-hover:text-pink-500 nc-copy-project-info" />
<MdiScriptTextKeyOutline class="group-hover:text-pink-500" />
Copy Auth Token
</div>
</a-menu-item>
@ -231,55 +231,17 @@ definePageMeta({
class="nc-project-menu-item group"
@click="toggleDialog(true, 'teamAndAuth')"
>
<MdiCog class="group-hover:text-pink-500 nc-team-settings" />
<MdiCog class="group-hover:text-pink-500" />
Team & Settings
</div>
</a-menu-item>
<a-menu-divider />
<template v-if="signedIn && !isSharedBase">
<a-sub-menu v-if="isUIAllowed('previewAs')" key="account">
<template #title>
<div class="nc-project-menu-item group">
<MdiAccount class="group-hover:text-pink-500 nc-project-preview" />
Account
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-menu class="!py-0 dark:(!bg-gray-800) leading-8 !rounded">
<a-menu-item key="0" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="nc-project-menu-item group no-underline" to="/user">
<MdiAt class="mt-1 group-hover:text-pink-500" />&nbsp;
<span class="prose">{{ email }}</span>
</nuxt-link>
</a-menu-item>
<a-menu-item key="1" class="!rounded-b">
<div v-t="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:(!text-pink-500)" />&nbsp;
<span class="prose">
{{ $t('general.signOut') }}
</span>
</div>
</a-menu-item>
</a-menu>
</a-sub-menu>
</template>
<a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as" v-t="['c:navdraw:preview-as']">
<a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as">
<template #title>
<div class="nc-project-menu-item group">
<MdiFileEyeOutline class="group-hover:text-pink-500 nc-project-preview" />
<div v-t="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiFileEyeOutline class="group-hover:text-pink-500" />
Preview Project As
<div class="flex-1" />
@ -295,7 +257,7 @@ definePageMeta({
<GeneralPreviewAs />
</a-sub-menu>
<a-sub-menu v-if="isUIAllowed('previewAs')" key="language">
<a-sub-menu key="language" class="lang-menu scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0">
<template #title>
<div class="nc-project-menu-item group">
<MaterialSymbolsTranslate class="group-hover:text-pink-500 nc-language" />
@ -309,12 +271,49 @@ definePageMeta({
</template>
<template #expandIcon></template>
<GeneralLanguage sub-menu />
<GeneralLanguageMenu />
</a-sub-menu>
<template v-if="signedIn && !isSharedBase">
<a-sub-menu v-if="isUIAllowed('previewAs')" key="account">
<template #title>
<div class="nc-project-menu-item group">
<MdiAccount class="group-hover:text-pink-500" />
Account
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-menu-item key="0" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="nc-project-menu-item group no-underline" to="/user">
<MdiAt class="mt-1 group-hover:text-pink-500" />&nbsp;
<span class="prose-sm">{{ email }}</span>
</nuxt-link>
</a-menu-item>
<a-menu-item key="1" class="!rounded-b">
<div v-t="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:(!text-pink-500)" />&nbsp;
<span class="prose-sm">
{{ $t('general.signOut') }}
</span>
</div>
</a-menu-item>
</a-sub-menu>
</template>
</a-menu-item-group>
</a-menu>
</template>
</a-dropdown>
<div class="nc-sidebar-left-toggle-icon hover:after:bg-primary/75 group nc-sidebar-add-row flex align-center px-2">
<MdiBackburger
class="cursor-pointer transform transition-transform duration-500"
@ -337,18 +336,6 @@ definePageMeta({
</template>
<style lang="scss" scoped>
: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);
}
:global(#nc-sidebar-left .ant-layout-sider-collapsed) {
@apply !w-0 !max-w-0 !min-w-0 overflow-x-hidden;
}
@ -356,6 +343,7 @@ definePageMeta({
.nc-left-sidebar {
.nc-sidebar-left-toggle-icon {
@apply opacity-0 transition-opactity duration-200 transition-color text-white/80 hover:text-white/100;
.nc-left-sidebar {
@apply !border-r-0;
}

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

@ -75,6 +75,7 @@ const { isOpen, toggle } = useSidebar()
:deep(.nc-root-tabs) {
& > .ant-tabs-nav {
@apply !mb-0 before:(!border-b-0);
.ant-tabs-extra-content {
@apply !bg-white/0;
}
@ -106,24 +107,6 @@ const { isOpen, toggle } = useSidebar()
}
}
.nc-add-project-menu {
: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-menu-item-selected) {
@apply text-inherit !bg-inherit;
}

44
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -557,6 +557,13 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
colBody.colOptions.options.push({ title: '' });
}
}
// Trim end of enum/set
if (colBody.dt === 'enum' || colBody.dt === 'set') {
for (const opt of colBody.colOptions.options) {
opt.title = opt.title.trimEnd()
}
}
if (colBody.uidt === UITypes.SingleSelect) {
colBody.dtxp = (colBody.colOptions?.options.length)
@ -720,21 +727,6 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
) {
colBody = getColumnPropsFromUIDT(colBody, base);
if (colBody.uidt === UITypes.SingleSelect) {
colBody.dtxp = (colBody.colOptions?.options.length)
? `${colBody.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}`
: '';
} else if (colBody.uidt === UITypes.MultiSelect){
colBody.dtxp = (colBody.colOptions?.options.length)
? `${colBody.colOptions.options.map((o) => {
if(o.title.includes(',')) {
NcError.badRequest('Illegal char(\',\') for MultiSelect');
}
return `'${o.title.replace(/'/gi, '\'\'')}'`;
}).join(',')}`
: '';
}
const baseModel = await Model.getBaseModelSQL({
id: table.id,
dbDriver: NcConnectionMgrv2.get(base)
@ -809,6 +801,28 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
}
}
// Trim end of enum/set
if (colBody.dt === 'enum' || colBody.dt === 'set') {
for (const opt of colBody.colOptions.options) {
opt.title = opt.title.trimEnd()
}
}
if (colBody.uidt === UITypes.SingleSelect) {
colBody.dtxp = (colBody.colOptions?.options.length)
? `${colBody.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}`
: '';
} else if (colBody.uidt === UITypes.MultiSelect){
colBody.dtxp = (colBody.colOptions?.options.length)
? `${colBody.colOptions.options.map((o) => {
if(o.title.includes(',')) {
NcError.badRequest('Illegal char(\',\') for MultiSelect');
}
return `'${o.title.replace(/'/gi, '\'\'')}'`;
}).join(',')}`
: '';
}
// Handle option delete
for (const option of column.colOptions.options.filter(oldOp => colBody.colOptions.options.find(newOp => newOp.id === oldOp.id) ? false : true)) {
if (!supportedDrivers.includes(driverType) && column.uidt === UITypes.MultiSelect) {

8
packages/nocodb/src/lib/models/Column.ts

@ -246,6 +246,10 @@ export default class Column<T = any> implements ColumnType {
}
} else {
for (const [i, option] of column.colOptions.options.entries() || [].entries()) {
// Trim end of enum/set
if (column.dt === 'enum' || column.dt === 'set') {
option.title = option.title.trimEnd();
}
await SelectOption.insert(
{
...option,
@ -274,6 +278,10 @@ export default class Column<T = any> implements ColumnType {
}
} else {
for (const [i, option] of column.colOptions.options.entries() || [].entries()) {
// Trim end of enum/set
if (column.dt === 'enum' || column.dt === 'set') {
option.title = option.title.trimEnd();
}
await SelectOption.insert(
{
...option,

2
packages/nocodb/src/lib/models/Model.ts

@ -94,7 +94,7 @@ export default class Model implements TableType {
public static async insert(
projectId,
baseId,
model: TableReqType & {
model: Partial<TableReqType> & {
mm?: boolean;
created_at?: any;
updated_at?: any;

Loading…
Cancel
Save