Browse Source

Merge branch 'develop' into feat/gui-v2-form-view

pull/3030/head
Wing-Kam Wong 2 years ago
parent
commit
0adb7d7f6a
  1. 2
      packages/nc-gui-v2/assets/style-v2.scss
  2. 3
      packages/nc-gui-v2/components.d.ts
  3. 4
      packages/nc-gui-v2/components/cell/MultiSelect.vue
  4. 2
      packages/nc-gui-v2/components/cell/attachment/Carousel.vue
  5. 30
      packages/nc-gui-v2/components/cell/attachment/index.vue
  6. 52
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  7. 2
      packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue
  8. 6
      packages/nc-gui-v2/components/dlg/AirtableImport.vue
  9. 2
      packages/nc-gui-v2/components/dlg/QuickImport.vue
  10. 18
      packages/nc-gui-v2/components/dlg/TableCreate.vue
  11. 6
      packages/nc-gui-v2/components/dlg/TableRename.vue
  12. 8
      packages/nc-gui-v2/components/general/MiniSidebar.vue
  13. 2
      packages/nc-gui-v2/components/smartsheet-column/CurrencyOptions.vue
  14. 2
      packages/nc-gui-v2/components/smartsheet-column/DurationOptions.vue
  15. 50
      packages/nc-gui-v2/components/smartsheet-column/EditOrAdd.vue
  16. 44
      packages/nc-gui-v2/components/smartsheet-column/LookupOptions.vue
  17. 2
      packages/nc-gui-v2/components/smartsheet-column/RatingOptions.vue
  18. 18
      packages/nc-gui-v2/components/smartsheet-column/RollupOptions.vue
  19. 62
      packages/nc-gui-v2/components/smartsheet-header/Menu.vue
  20. 2
      packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue
  21. 3
      packages/nc-gui-v2/components/smartsheet-header/VirtualCellIcon.vue
  22. 3
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  23. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue
  24. 37
      packages/nc-gui-v2/components/smartsheet-toolbar/FieldListAutoCompleteDropdown.vue
  25. 5
      packages/nc-gui-v2/components/smartsheet-toolbar/MoreActions.vue
  26. 1
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  27. 9
      packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue
  28. 1
      packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue
  29. 10
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  30. 6
      packages/nc-gui-v2/components/smartsheet/Gallery.vue
  31. 40
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  32. 13
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  33. 4
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  34. 3
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  35. 4
      packages/nc-gui-v2/components/webhook/Test.vue
  36. 7
      packages/nc-gui-v2/composables/useColumnCreateStore.ts
  37. 10
      packages/nc-gui-v2/layouts/base.vue
  38. 10
      packages/nc-gui-v2/pages/index/user/index.vue
  39. 4
      packages/nc-gui-v2/pages/index/user/index/index.vue
  40. 6
      packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue
  41. 4
      packages/nc-gui-v2/pages/project/index/[id].vue
  42. 6
      packages/nc-gui-v2/pages/project/index/create.vue
  43. 2
      packages/nc-gui-v2/pages/projects/index.vue
  44. 9
      packages/nocodb-sdk/src/lib/Api.ts
  45. 2
      packages/nocodb/src/lib/models/View.ts
  46. 17
      scripts/sdk/swagger.json

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

@ -75,7 +75,7 @@ html {
// menu item styling
.nc-menu-item {
@apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
@apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
}
.nc-sidebar-right-item {

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

@ -41,6 +41,7 @@ declare module '@vue/runtime-core' {
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARate: typeof import('ant-design-vue/es')['Rate']
@ -90,6 +91,7 @@ declare module '@vue/runtime-core' {
MdiDatabase: typeof import('~icons/mdi/database')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDrag: typeof import('~icons/mdi/drag')['default']
@ -114,6 +116,7 @@ declare module '@vue/runtime-core' {
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiSearch: typeof import('~icons/mdi/search')['default']
MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiStar: typeof import('~icons/mdi/star')['default']

4
packages/nc-gui-v2/components/cell/MultiSelect.vue

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, inject } from '#imports'
import { ColumnInj, EditModeInj } from '~/context'
import { ColumnInj } from '~/context'
interface Props {
modelValue: string | null
@ -11,8 +11,6 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)
const isForm = inject<boolean>('isForm', false)
const editEnabled = inject(EditModeInj, ref(false))
const options = computed(() => column?.value?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [])

2
packages/nc-gui-v2/components/cell/attachment/Carousel.vue

@ -2,7 +2,7 @@
import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils'
import { isImage } from '~/utils'
import { computed, onClickOutside, ref, watch } from '#imports'
import { computed, onClickOutside, ref } from '#imports'
import MaterialSymbolsArrowCircleRightRounded from '~icons/material-symbols/arrow-circle-right-rounded'
import MaterialSymbolsArrowCircleLeftRounded from '~icons/material-symbols/arrow-circle-left-rounded'
import MdiCloseCircle from '~icons/mdi/close-circle'

30
packages/nc-gui-v2/components/cell/attachment/index.vue

@ -6,21 +6,17 @@ import { useSortable } from './sort'
import Carousel from './Carousel.vue'
import { onMounted, ref, useDropZone, watch } from '#imports'
import { isImage, openLink } from '~/utils'
import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file'
import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand'
import MaterialSymbolsFileCopyOutline from '~icons/material-symbols/file-copy-outline'
import MdiReload from '~icons/mdi/reload'
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file'
interface Props {
modelValue: string | Record<string, any>[] | null
rowIndex: number
}
interface Emits {
(event: 'update:modelValue', value: string | Record<string, any>): void
}
const { modelValue } = defineProps<Props>()
const { modelValue, rowIndex } = defineProps<Props>()
const emits = defineEmits<Emits>()
@ -60,7 +56,7 @@ onKeyDown('Escape', () => {
/** if possible, on mounted we try to fetch the relevant `td` cell to use as a dropzone */
onMounted(() => {
if (typeof document !== 'undefined') {
dropZoneRef.value = document.querySelector(`td[data-col="${column.value.id}"]`) as HTMLTableDataCellElement
dropZoneRef.value = document.querySelector(`td[data-key="${rowIndex}${column.value.id}"]`) as HTMLTableDataCellElement
}
})
</script>
@ -73,7 +69,7 @@ onMounted(() => {
<general-overlay
v-model="isOverDropZone"
inline
:target="`td[data-col='${column.id}']`"
:target="`td[data-key='${rowIndex}${column.id}']`"
class="text-white text-lg ring ring-pink-500 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
>
<MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here
@ -100,12 +96,15 @@ onMounted(() => {
</div>
<template v-if="visibleItems.length">
<div ref="sortableRef" :class="{ dragging }" class="flex flex-wrap gap-2 p-1 scrollbar-thin-dull">
<div
ref="sortableRef"
:class="{ dragging }"
class="flex justify-center items-center flex-wrap gap-2 p-1 scrollbar-thin-dull max-h-[150px] overflow-scroll"
>
<div
v-for="(item, i) of visibleItems"
:id="item.url"
:key="item.url || item.title"
style="flex: 1 1 50px"
:class="isImage(item.title, item.mimetype) ? '' : 'border-1 rounded'"
class="nc-attachment flex items-center justify-center min-h-[50px]"
>
@ -120,7 +119,7 @@ onMounted(() => {
placeholder
:alt="item.title || `#${i}`"
:src="item.url || item.data"
class="ring-1 ring-gray-300 rounded"
class="ring-1 ring-gray-300 rounded max-h-[50px] max-w-[50px]"
@click="selectedImage = item"
/>
@ -137,10 +136,7 @@ onMounted(() => {
<a-tooltip v-else placement="bottom">
<template #title> View attachments </template>
<MaterialArrowExpandIcon
class="select-none transform group-hover:(text-pink-500 scale-120)"
@click.stop="modalVisible = true"
/>
<MdiArrowExpand class="select-none transform group-hover:(text-pink-500 scale-120)" @click.stop="modalVisible = true" />
</a-tooltip>
</div>
</template>
@ -152,6 +148,10 @@ onMounted(() => {
<style lang="scss">
.nc-cell {
.nc-attachment-cell {
.nc-attachment {
@apply w-[50px] h-[50px] min-h-[50px] min-w-[50px];
}
.ghost,
.ghost > * {
@apply !pointer-events-none;

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

@ -1,27 +1,25 @@
<script setup lang="ts">
import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs'
import { useToast } from 'vue-toastification'
import { useProject, useTable, useTabs, watchEffect } from '#imports'
import { useNuxtApp, useRoute } from '#app'
import { computed, useProject, useTable, useTabs, watchEffect } from '#imports'
import { TabType } from '~/composables'
import MdiTable from '~icons/mdi/table'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large'
import MdiMenuDown from '~icons/mdi/chevron-down'
import MdiPlus from '~icons/mdi/plus-circle-outline'
import MdiDrag from '~icons/mdi/drag-vertical'
import MdiMenuIcon from '~icons/mdi/dots-vertical'
import MdiDrag from '~icons/mdi/drag-vertical'
import MdiPlus from '~icons/mdi/plus-circle-outline'
const { addTab } = useTabs()
const toast = useToast()
const { $api, $e } = useNuxtApp()
const route = useRoute()
const { tables, loadTables } = useProject(route.params.projectId as string)
const { activeTab } = useTabs()
const { deleteTable } = useTable()
const tablesById = $computed<Record<string, TableType>>(() =>
@ -32,13 +30,11 @@ const tablesById = $computed<Record<string, TableType>>(() =>
)
const showTableList = ref(true)
const tableCreateDlg = ref(false)
let key = $ref(0)
const menuRef = $ref<HTMLLIElement>()
let key = $ref(0)
let sortable: Sortable
// todo: replace with vuedraggable
@ -106,13 +102,11 @@ 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
@ -120,24 +114,24 @@ const setMenuContext = (type: 'table' | 'main', value?: any) => {
}
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
})
</script>
<template>
@ -186,11 +180,10 @@ const addTableTab = (table: TableType) => {
:key="table.id"
v-t="['a:table:open']"
:class="[
{ hidden: !filteredTables?.includes(table) },
{ hidden: !filteredTables?.includes(table), active: activeTable === table.title },
`nc-project-tree-tbl nc-project-tree-tbl-${table.title}`,
route.params.title && route.params.title.includes(table.title) ? 'bg-blue-500/15' : '',
]"
class="pl-5 pr-3 py-2 text-sm cursor-pointer group"
class="nc-tree-item pl-5 pr-3 py-2 text-sm cursor-pointer group"
:data-order="table.order"
:data-id="table.id"
@click="addTableTab(table)"
@ -254,7 +247,7 @@ const addTableTab = (table: TableType) => {
</div>
</template>
<style lang="scss" scoped>
<style scoped>
.nc-treeview-container {
@apply h-[calc(100vh_-_var(--header-height))];
}
@ -299,4 +292,23 @@ const addTableTab = (table: TableType) => {
@apply !bg-primary/25 text-primary;
}
}
.nc-tree-item {
@apply relative cursor-pointer after:(pointer-events-none content-[''] absolute top-0 left-0 w-full h-full right-0 !bg-current transition transition-opactity duration-100 opacity-0);
}
.nc-tree-item svg {
@apply text-gray-500;
}
.nc-tree-item.active {
@apply !text-primary after:(!opacity-5);
svg {
@apply !text-primary;
}
}
.nc-tree-item:hover {
@apply !text-grey after:(!opacity-2);
}
</style>

2
packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue

@ -32,7 +32,7 @@ async function loadTableList() {
isLoading = true
// TODO includeM2M
tables = await $api.project.modelVisibilityList(project.value?.id, {
includeM2M: true,
includeM2M: false,
})
} catch (e) {
console.error(e)

6
packages/nc-gui-v2/components/dlg/AirtableImport.vue

@ -25,9 +25,7 @@ const { $state } = useNuxtApp()
const toast = useToast()
const { sqlUi, project, loadTables } = useProject()
const loading = ref(false)
const { project, loadTables } = useProject()
const showGoToDashboardButton = ref(false)
@ -78,7 +76,7 @@ const dialogShow = computed({
const useForm = Form.useForm
const { resetFields, validate, validateInfos } = useForm(syncSource, validators)
const { validateInfos } = useForm(syncSource, validators)
const disableImportButton = computed(() => {
return !syncSource.value.details.apiKey || !syncSource.value.details.syncSourceUrlOrId

2
packages/nc-gui-v2/components/dlg/QuickImport.vue

@ -68,7 +68,7 @@ const validators = computed(() => {
}
})
const { resetFields, validate, validateInfos } = useForm(importState, validators)
const { validate, validateInfos } = useForm(importState, validators)
const importMeta = computed(() => {
if (IsImportTypeExcel.value) {

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

@ -1,6 +1,5 @@
<script setup lang="ts">
import { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { onMounted, useProject, useTable, useTabs } from '#imports'
import { validateTableName } from '~/utils/validation'
import { TabType } from '~/composables'
@ -15,12 +14,6 @@ const emit = defineEmits(['update:modelValue'])
const dialogShow = useVModel(props, 'modelValue', emit)
const toast = useToast()
const valid = ref(false)
const isIdToggleAllowed = ref(false)
const isAdvanceOptVisible = ref(false)
const { addTab } = useTabs()
@ -38,18 +31,9 @@ const { table, createTable, generateUniqueTitle, tables, project } = useTable(as
dialogShow.value = false
})
const prefix = computed(() => project?.value?.prefix || '')
const validateDuplicateAlias = (v: string) => {
return (tables?.value || []).every((t) => t.title !== (v || '')) || 'Duplicate table alias'
}
const validateLeadingOrTrailingWhiteSpace = (v: string) => {
return !/^\s+|\s+$/.test(v) || 'Leading or trailing whitespace not allowed in table name'
}
const validateDuplicate = (v: string) => {
return (tables?.value || []).every((t) => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name'
}
const inputEl = ref<HTMLInputElement>()
const useForm = Form.useForm
@ -60,7 +44,7 @@ const validators = computed(() => {
table_name: [validateTableName],
}
})
const { resetFields, validate, validateInfos } = useForm(table, validators)
const { validateInfos } = useForm(table, validators)
onMounted(() => {
generateUniqueTitle()

6
packages/nc-gui-v2/components/dlg/TableRename.vue

@ -28,9 +28,7 @@ const dialogShow = computed({
const { updateTab } = useTabs()
const { loadTables } = useProject()
const { project, tables } = useProject()
const prefix = computed(() => project?.value?.prefix || '')
const { tables } = useProject()
const inputEl = $ref<any>()
let loading = $ref(false)
@ -60,7 +58,7 @@ const validators = computed(() => {
],
}
})
const { resetFields, validate, validateInfos } = useForm(formState, validators)
const { validateInfos } = useForm(formState, validators)
watchEffect(() => {
if (tableMeta?.title) formState.title = tableMeta?.title

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

@ -1,12 +1,8 @@
<script lang="ts" setup>
import { breakpointsTailwind } from '@vueuse/core'
import { navigateTo } from '#app'
import { computed, useBreakpoints, useGlobal, useProject, useRoute, useSidebar } from '#imports'
import { computed, useGlobal, useProject, useRoute, useSidebar } from '#imports'
/** get current breakpoints (for enabling sidebar) */
const breakpoints = useBreakpoints(breakpointsTailwind)
const { signOut, signedIn, isLoading, user } = useGlobal()
const { signOut, signedIn, user } = useGlobal()
const { isOpen } = useSidebar({ isOpen: true })

2
packages/nc-gui-v2/components/smartsheet-column/CurrencyOptions.vue

@ -7,7 +7,7 @@ interface Option {
value: string
}
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow()
const { formState, validateInfos, setAdditionalValidations } = useColumnCreateStoreOrThrow()
const validators = {
'meta.currency_locale': [

2
packages/nc-gui-v2/components/smartsheet-column/DurationOptions.vue

@ -2,7 +2,7 @@
import { useColumnCreateStoreOrThrow } from '#imports'
import { durationOptions } from '@/utils'
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow()
const { formState } = useColumnCreateStoreOrThrow()
const durationOptionList =
durationOptions.map((o) => ({

50
packages/nc-gui-v2/components/smartsheet-column/EditOrAdd.vue

@ -8,7 +8,7 @@ import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
import MdiIdentifierIcon from '~icons/mdi/identifier'
interface Props {
editColumnDropdown: boolean
editColumnDropdown?: boolean
}
const { editColumnDropdown } = defineProps<Props>()
@ -21,20 +21,13 @@ const { getMeta } = useMetas()
const formulaOptionsRef = ref()
const {
formState,
resetFields,
validate,
validateInfos,
onUidtOrIdTypeChange,
onAlter,
addOrUpdate,
generateNewColumnMeta,
isEdit,
} = useColumnCreateStoreOrThrow()
const { formState, validateInfos, onUidtOrIdTypeChange, onAlter, addOrUpdate, generateNewColumnMeta, isEdit } =
useColumnCreateStoreOrThrow()
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup]
const uiTypesOptions = computed<typeof uiTypes>(() => {
return [
...uiTypes.filter((t) => !isEdit.value || !t.virtual),
@ -65,6 +58,11 @@ function onCancel() {
}
}
async function onSubmit() {
await addOrUpdate(reloadMetaAndData)
advancedOptions.value = false
}
// create column meta if it's a new column
watchEffect(() => {
if (!isEdit.value) {
@ -82,6 +80,7 @@ watchEffect(() => {
antInput.value.select()
}, 300)
}
advancedOptions.value = false
})
watch(
@ -97,7 +96,7 @@ watch(
</script>
<template>
<div class="max-w-[450px] min-w-[350px] w-max max-h-[95vh] bg-white shadow p-4 overflow-auto" @click.stop>
<div class="max-w-[550px] min-w-[450px] w-max max-h-[95vh] bg-white shadow p-4 overflow-auto" @click.stop>
<a-form v-model="formState" name="column-create-or-edit" layout="vertical">
<a-form-item :label="$t('labels.columnName')" v-bind="validateInfos.column_name">
<a-input
@ -108,7 +107,10 @@ watch(
@input="onAlter(8)"
/>
</a-form-item>
<a-form-item :label="$t('labels.columnType')">
<a-form-item
v-if="!(editColumnDropdown && !!onlyNameUpdateOnEditColumns.find((col) => col === formState.uidt))"
:label="$t('labels.columnType')"
>
<a-select
v-model:value="formState.uidt"
show-search
@ -130,10 +132,12 @@ watch(
<SmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" />
<SmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" />
<SmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" />
<SmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" />
<SmartsheetColumnLookupOptions v-if="!editColumnDropdown && formState.uidt === UITypes.Lookup" />
<SmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" />
<SmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" />
<SmartsheetColumnLinkedToAnotherRecordOptions v-if="formState.uidt === UITypes.LinkToAnotherRecord" />
<SmartsheetColumnRollupOptions v-if="!editColumnDropdown && formState.uidt === UITypes.Rollup" />
<SmartsheetColumnLinkedToAnotherRecordOptions
v-if="!editColumnDropdown && formState.uidt === UITypes.LinkToAnotherRecord"
/>
<SmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<SmartsheetColumnPercentOptions v-if="formState.uidt === UITypes.Percent" />
@ -164,17 +168,7 @@ watch(
<!-- Cancel -->
{{ $t('general.cancel') }}
</a-button>
<a-button
html-type="submit"
type="primary"
size="small"
@click="
() => {
addOrUpdate(reloadMetaAndData)
advancedOptions.value = false
}
"
>
<a-button html-type="submit" type="primary" size="small" @click="onSubmit">
<!-- Save -->
{{ $t('general.save') }}
</a-button>

44
packages/nc-gui-v2/components/smartsheet-column/LookupOptions.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import { useColumnCreateStoreOrThrow } from '#imports'
import { MetaInj } from '~/context'
@ -27,17 +28,17 @@ const refTables = $computed(() => {
return []
}
// todo: type issues with ColumnType so we have to cast to any
return (
meta.columns
?.filter((c: any) => c.uidt === UITypes.LinkToAnotherRecord && c.colOptions?.type !== 'bt' && !c.system)
.map((c) => ({
col: c.colOptions,
column: c,
...tables.find((t) => t.id === (c.colOptions as any)?.fk_related_model_id),
}))
.filter((table: any) => table.col?.fk_related_model_id === table.id && !table.mm) ?? []
)
return meta.columns
.filter((c: ColumnType) => c.uidt === UITypes.LinkToAnotherRecord && !c.system)
.map<TableType & { col: LinkToAnotherRecordType; column: ColumnType }>((c: ColumnType) => ({
col: c.colOptions,
column: c,
...tables.find((t) => t.id === (c.colOptions as LinkToAnotherRecordType).fk_related_model_id),
}))
.filter(
(table: TableType & { col: LinkToAnotherRecordType; column: ColumnType }) =>
table.col.fk_related_model_id === table.id && !table.mm,
)
})
const columns = $computed(() => {
@ -46,7 +47,7 @@ const columns = $computed(() => {
return []
}
return metas[selectedTable.id].columns.filter((c: any) => !isSystemColumn(c))
return metas[selectedTable.id].columns.filter((c) => !isSystemColumn(c))
})
</script>
@ -54,17 +55,22 @@ const columns = $computed(() => {
<div class="p-4 w-full flex flex-col border-2 mb-2 mt-4">
<div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-select v-model:value="formState.fk_relation_column_id" size="small" @change="onDataTypeChange">
<a-select-option v-for="(table, index) of refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row items-center space-x-0.5 h-full">
<div class="font-weight-bold text-[0.7rem]">{{ table.column.title }}</div>
<div class="text-[0.5rem]">({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})</div>
<a-select
v-model:value="formState.fk_relation_column_id"
size="small"
dropdown-class-name="!w-64"
@change="onDataTypeChange"
>
<a-select-option v-for="(table, index) in refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row space-x-0.5 h-full pb-0.5 items-center justify-between">
<div class="font-semibold text-xs">{{ table.column.title }}</div>
<div class="text-[0.65rem] text-gray-600">
{{ relationNames[table.col.type] }} {{ table.title || table.table_name }}
</div>
</div>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="flex w-1/2" :label="$t('labels.childColumn')" v-bind="validateInfos.fk_lookup_column_id">
<a-select
v-model:value="formState.fk_lookup_column_id"

2
packages/nc-gui-v2/components/smartsheet-column/RatingOptions.vue

@ -3,7 +3,7 @@ import { Sketch } from '@ckpack/vue-color'
import { useColumnCreateStoreOrThrow } from '#imports'
import { enumColor, getMdiIcon } from '@/utils'
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow()
const { formState } = useColumnCreateStoreOrThrow()
// cater existing v1 cases
const iconList = [

18
packages/nc-gui-v2/components/smartsheet-column/RollupOptions.vue

@ -4,7 +4,6 @@ import { inject, useColumnCreateStoreOrThrow, useMetas, useProject } from '#impo
import { MetaInj } from '~/context'
const { formState, validateInfos, onDataTypeChange, setAdditionalValidations } = $(useColumnCreateStoreOrThrow())
const { tables } = $(useProject())
const meta = $(inject(MetaInj)!)
@ -69,11 +68,18 @@ const columns = $computed(() => {
<div class="p-4 w-full flex flex-col border-2 mb-2 mt-4">
<div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-select v-model:value="formState.fk_relation_column_id" size="small" @change="onDataTypeChange">
<a-select-option v-for="(table, index) of refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row items-center space-x-0.5">
<div class="font-weight-bold text-xs">{{ table.column.title }}</div>
<div class="text-[0.45rem]">({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})</div>
<a-select
v-model:value="formState.fk_relation_column_id"
size="small"
dropdown-class-name="!w-64"
@change="onDataTypeChange"
>
<a-select-option v-for="(table, index) in refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row space-x-0.5 h-full pb-0.5 items-center justify-between">
<div class="font-semibold text-xs">{{ table.column.title }}</div>
<div class="text-[0.65rem] text-gray-600">
({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})
</div>
</div>
</a-select-option>
</a-select>

62
packages/nc-gui-v2/components/smartsheet-header/Menu.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { onClickOutside } from '@vueuse/core'
import { Modal } from 'ant-design-vue'
import { inject } from 'vue'
import { useI18n } from 'vue-i18n'
@ -55,38 +56,61 @@ function onVisibleChange() {
// by clicking cancel button
editColumnDropdown.value = true
}
const editOrAddCard = ref()
onClickOutside(editOrAddCard, () => {
editColumnDropdown.value = false
})
</script>
<template>
<a-dropdown v-model:visible="editColumnDropdown" :trigger="['click']" @visible-change="onVisibleChange">
<span />
<template #overlay>
<SmartsheetColumnEditOrAdd :edit-column-dropdown="editColumnDropdown" @click.stop @cancel="editColumnDropdown = false" />
<SmartsheetColumnEditOrAdd
ref="editOrAddCard"
:edit-column-dropdown="editColumnDropdown"
@click.stop
@cancel="editColumnDropdown = false"
/>
</template>
</a-dropdown>
<a-dropdown :trigger="['hover']">
<MdiMenuDownIcon class="text-grey nc-ui-dt-dropdown" />
<template #overlay>
<div class="shadow bg-white">
<div class="nc-column-edit nc-menu-item" @click="editColumnDropdown = true">
<MdiEditIcon class="text-primary" />
<!-- Edit -->
{{ $t('general.edit') }}
</div>
<div v-if="!virtual" v-t="['a:column:set-primary']" class="nc-menu-item" @click="setAsPrimaryValue">
<MdiStarIcon class="text-primary" />
<a-menu class="shadow bg-white">
<a-menu-item @click="editColumnDropdown = true">
<div class="nc-column-edit nc-header-menu-item">
<MdiEditIcon class="text-primary" />
<!-- Edit -->
{{ $t('general.edit') }}
</div>
</a-menu-item>
<a-menu-item v-if="!virtual" v-t="['a:column:set-primary']" @click="setAsPrimaryValue">
<div class="nc-column-edit nc-header-menu-item">
<MdiStarIcon class="text-primary" />
<!-- todo : tooltip -->
<!-- Set as Primary value -->
{{ $t('activity.setPrimary') }}
<!-- todo : tooltip -->
<!-- Set as Primary value -->
{{ $t('activity.setPrimary') }}
</div>
<!-- <span class="caption font-weight-bold">Primary value will be shown in place of primary key</span> -->
</div>
<div class="nc-column-delete nc-menu-item" @click="deleteColumn">
<MdiDeleteIcon class="text-error" />
<!-- Delete -->
{{ $t('general.delete') }}
</div>
</div>
</a-menu-item>
<a-menu-item @click="deleteColumn">
<div class="nc-column-delete nc-header-menu-item">
<MdiDeleteIcon class="text-error" />
<!-- Delete -->
{{ $t('general.delete') }}
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<style scoped>
.nc-header-menu-item {
@apply text-xs flex items-center px-1 py-2 gap-1;
}
</style>

2
packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue

@ -18,7 +18,7 @@ const { metas } = useMetas()
const meta = inject(MetaInj)
const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column)
const { isLookup, isBt, isRollup, isMm, isHm, isFormula } = useVirtualCell(column)
const colOptions = $computed(() => column.value?.colOptions)
const tableTile = $computed(() => meta?.value?.title)

3
packages/nc-gui-v2/components/smartsheet-header/VirtualCellIcon.vue

@ -11,6 +11,7 @@ import FormulaIcon from '~icons/mdi/math-integral'
import RollupIcon from '~icons/mdi/movie-roll'
import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
@ -34,7 +35,7 @@ const icon = computed(() => {
case UITypes.Formula:
return FormulaIcon
case UITypes.Lookup:
return GenericIcon
return TableColumnPlusBefore
case UITypes.Rollup:
return RollupIcon
case UITypes.Count:

3
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -5,7 +5,7 @@ import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import { useNuxtApp } from '#app'
import { inject, useViewFilters } from '#imports'
import { comparisonOpList } from '~/utils/filterUtils'
import { ActiveViewInj, IsLockedInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import { ActiveViewInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import MdiDeleteIcon from '~icons/mdi/close-box'
import MdiAddIcon from '~icons/mdi/plus'
@ -16,7 +16,6 @@ const emit = defineEmits(['update:filtersLength'])
const meta = inject(MetaInj)
const activeView = inject(ActiveViewInj)
const reloadDataHook = inject(ReloadViewDataHookInj)
const isLocked = inject(IsLockedInj)
// todo: replace with inject or get from state
const shared = ref(false)

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

@ -1,12 +1,10 @@
<script setup lang="ts">
import { watchEffect } from '@vue/runtime-core'
import type ColumnFilter from './ColumnFilter.vue'
import { useState } from '#app'
import { ActiveViewInj, IsLockedInj } from '~/context'
import MdiFilterIcon from '~icons/mdi/filter-outline'
import MdiMenuDownIcon from '~icons/mdi/menu-down'
const autoApplyFilter = useState('autoApplyFilter', () => false)
const isLocked = inject(IsLockedInj)
const activeView = inject(ActiveViewInj)

37
packages/nc-gui-v2/components/smartsheet-toolbar/FieldListAutoCompleteDropdown.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { SelectProps } from 'ant-design-vue'
import { isVirtualCol } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import { computed } from 'vue'
import { MetaInj } from '~/context'
import VirtualCellIcon from '~/components/smartsheet-header/VirtualCellIcon.vue'
@ -8,9 +9,10 @@ import CellIcon from '~/components/smartsheet-header/CellIcon.vue'
interface Props {
modelValue?: string
isSort?: boolean
}
const { modelValue } = defineProps<Props>()
const { modelValue, isSort } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
@ -48,14 +50,26 @@ const localValue = computed({
},
} */
const options = computed<SelectProps['options']>(() =>
meta?.value?.columns?.map((c) => ({
value: c.id,
label: c.title,
icon: h(isVirtualCol(c) ? VirtualCellIcon : CellIcon, {
columnMeta: c,
}),
c,
})),
meta?.value?.columns
?.filter((c: ColumnType) => {
/** ignore hasmany and manytomany relations if it's using within sort menu */
if (isSort) {
return !(
c.uidt === UITypes.LinkToAnotherRecord && (c.colOptions as LinkToAnotherRecordType).type !== RelationTypes.BELONGS_TO
)
/** ignore vutual fields which are system fields ( mm relation ) */
} else {
return !c.colOptions || !c.system
}
})
.map((c: ColumnType) => ({
value: c.id,
label: c.title,
icon: h(isVirtualCol(c) ? VirtualCellIcon : CellIcon, {
columnMeta: c,
}),
c,
})),
)
const filterOption = (input: string, option: any) => {
@ -75,7 +89,8 @@ const filterOption = (input: string, option: any) => {
>
<a-select-option v-for="option in options" :key="option.value" :value="option.value">
<div class="flex gap-2 text-xs items-center align-center h-full">
<component :is="option.icon" class="min-w-5 !mx-0" /> <span class="min-w-0"> {{ option.label }}</span>
<component :is="option.icon" class="min-w-5 !mx-0" />
<span class="min-w-0"> {{ option.label }}</span>
</div>
</a-select-option>
</a-select>

5
packages/nc-gui-v2/components/smartsheet-toolbar/MoreActions.vue

@ -18,11 +18,6 @@ const sharedViewListDlg = ref(false)
// todo : replace with inject
const publicViewId = null
// TODO:: identify based on meta
const isView = ref(false)
const { isUIAllowed } = useUIPermission()
const { project } = useProject()
const { $api } = useNuxtApp()

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

@ -10,7 +10,6 @@ import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiOpenInNewIcon from '~icons/mdi/open-in-new'
import MdiCopyIcon from '~icons/mdi/content-copy'
const { isUIAllowed } = useUIPermission()
const { view, $api } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard()

9
packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue

@ -3,7 +3,6 @@ import { useClipboard } from '@vueuse/core'
import { ViewTypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { message } from 'ant-design-vue'
import { useRoute } from '#app'
import { onMounted, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiVisibilityOnIcon from '~icons/mdi/visibility'
@ -20,18 +19,14 @@ interface SharedViewType {
showPassword?: boolean
}
const { view, $api, meta } = useSmartsheetStoreOrThrow()
const { $api, meta } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard()
const toast = useToast()
const route = useRoute()
const { dashboardUrl } = useDashboard()
let isLoading = $ref(false)
// let activeSharedView = $ref(null)
const sharedViewList = ref<SharedViewType[]>()
const loadSharedViewsList = async () => {
isLoading = true
const list = await $api.dbViewShare.list(meta.value?.id as string)
console.log(unref(sharedViewList))
@ -49,8 +44,6 @@ const loadSharedViewsList = async () => {
// } else {
// activeSharedView = null
// }
isLoading = false
}
onMounted(loadSharedViewsList)

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

@ -59,6 +59,7 @@ watch(
v-model="sort.fk_column_id"
class="caption nc-sort-field-select"
:columns="columns"
is-sort
@click.stop
@update:model-value="saveOrUpdate(sort, i)"
/>

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

@ -10,14 +10,13 @@ interface Props {
column: ColumnType
modelValue: any
editEnabled: boolean
}
interface Emits {
(event: 'update:modelValue', value: any): void
rowIndex: number
}
const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled'])
const column = toRef(props, 'column')
provide(ColumnInj, column)
@ -25,6 +24,7 @@ provide(ColumnInj, column)
provide(EditModeInj, useVModel(props, 'editEnabled', emit))
let changed = $ref(false)
const syncValue = useDebounceFn(function () {
changed = false
emit('save')
@ -114,7 +114,7 @@ const syncAndNavigate = (dir: NavigateDir) => {
>
<CellTextArea v-if="isTextArea" v-model="vModel" />
<CellCheckbox v-else-if="isBoolean" v-model="vModel" />
<CellAttachment v-else-if="isAttachment" v-model="vModel" />
<CellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" />
<CellSingleSelect v-else-if="isSingleSelect" v-model="vModel" />
<CellMultiSelect v-else-if="isMultiSelect" v-model="vModel" />
<CellDatePicker v-else-if="isDate" v-model="vModel" />

6
packages/nc-gui-v2/components/smartsheet/Gallery.vue

@ -11,12 +11,6 @@ interface Attachment {
const meta = inject(MetaInj)
const view = inject(ActiveViewInj)
// todo: get from parent ( inject or use prop )
const isPublicView = false
const selected = reactive<{ row?: number | null; col?: number | null }>({})
const editEnabled = ref(false)
const { loadData, paginationData, formattedData: data, loadGalleryData, galleryData, changePage } = useViewData(meta, view as any)
provide(IsFormInj, false)

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

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { onClickOutside, useEventListener } from '@vueuse/core'
import type { ColumnType } from 'nocodb-sdk'
import { isVirtualCol } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import {
inject,
@ -228,6 +228,12 @@ useEventListener(document, 'keydown', onKeyDown)
/** On clicking outside of table reset active cell */
const smartTable = ref(null)
onClickOutside(smartTable, () => {
if (selected.col === null) return
const activeCol = fields.value[selected.col]
if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
selected.row = null
selected.col = null
})
@ -255,7 +261,7 @@ const onNavigate = (dir: NavigateDir) => {
<a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']">
<table ref="smartTable" class="xc-row-table nc-grid backgroundColorDefault" @contextmenu.prevent="contextMenu = true">
<thead>
<tr class="group">
<tr>
<th>
<div class="flex align-center w-[80px]">
<div class="group-hover:hidden" :class="{ hidden: selectedAllRecords }">#</div>
@ -264,6 +270,7 @@ const onNavigate = (dir: NavigateDir) => {
class="group-hover:flex w-full align-center"
>
<a-checkbox v-model:checked="selectedAllRecords" />
<span class="flex-1" />
</div>
</div>
@ -279,6 +286,7 @@ const onNavigate = (dir: NavigateDir) => {
@xcresized="resizingCol = null"
>
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" />
<SmartsheetHeaderCell v-else :column="col" />
</th>
<!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" -->
@ -287,6 +295,7 @@ const onNavigate = (dir: NavigateDir) => {
<div class="h-full w-[60px] flex align-center justify-center">
<MdiPlus class="text-sm" />
</div>
<template #overlay>
<SmartsheetColumnEditOrAdd @click.stop @cancel="addColumnDropdown = false" />
</template>
@ -295,7 +304,7 @@ const onNavigate = (dir: NavigateDir) => {
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) of data" :key="rowIndex" class="nc-grid-row">
<tr v-for="(row, rowIndex) of data" :key="rowIndex" class="nc-grid-row group">
<td key="row-index" class="caption nc-grid-cell group">
<div class="flex items-center w-[80px]">
<div class="group-hover:hidden" :class="{ hidden: row.rowMeta.selected }">{{ rowIndex + 1 }}</div>
@ -304,6 +313,7 @@ const onNavigate = (dir: NavigateDir) => {
class="group-hover:flex w-full items-center justify-between p-1"
>
<a-checkbox v-model:checked="row.rowMeta.selected" />
<span class="flex-1" />
<div class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:bg-primary/10">
<MdiArrowExpand class="select-none transform hover:(text-pink-500 scale-120)" />
</div>
@ -313,10 +323,11 @@ const onNavigate = (dir: NavigateDir) => {
<td
v-for="(columnObj, colIndex) of fields"
:key="rowIndex + columnObj.title"
class="cell pointer nc-grid-cell"
class="cell relative cursor-pointer nc-grid-cell"
:class="{
active: !isPublicView && selected.col === colIndex && selected.row === rowIndex,
}"
:data-key="rowIndex + columnObj.id"
:data-col="columnObj.id"
:data-title="columnObj.title"
@click="selectCell(rowIndex, colIndex)"
@ -338,6 +349,7 @@ const onNavigate = (dir: NavigateDir) => {
v-model="row.row[columnObj.title]"
:column="columnObj"
:edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex"
:row-index="rowIndex"
@update:edit-enabled="editEnabled = false"
@save="updateOrSaveRow(row, columnObj.title)"
@navigate="onNavigate"
@ -366,14 +378,18 @@ const onNavigate = (dir: NavigateDir) => {
</tbody>
</table>
<template #overlay>
<div class="bg-white shadow" @click="contextMenu = false">
<div v-if="contextMenuTarget" class="nc-menu-item" @click="deleteRow(contextMenuTarget.row)">Delete row</div>
<div class="nc-menu-item" @click="deleteSelectedRows">Delete all selected rows</div>
<div v-if="contextMenuTarget" class="nc-menu-item" @click="clearCell(contextMenuTarget)">Clear cell</div>
<div v-if="contextMenuTarget" class="nc-menu-item" @click="addEmptyRow(contextMenuTarget.row + 1)">
Insert new row
</div>
</div>
<a-menu class="bg-white shadow" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)"
><span class="text-xs">Delete row</span></a-menu-item
>
<a-menu-item @click="deleteSelectedRows"><span class="text-xs">Delete all selected rows</span></a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="clearCell(contextMenuTarget)"
><span class="text-xs">Clear cell</span>
</a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="addEmptyRow(contextMenuTarget.row + 1)">
<span class="text-xs">Insert new row</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>

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

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk'
import type { ViewType, ViewTypes } from 'nocodb-sdk'
import type { SortableEvent } from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue'
import { notification } from 'ant-design-vue'
@ -45,7 +45,7 @@ let isMarked = $ref<string | false>(false)
/** Watch currently active view, so we can mark it in the menu */
watch(activeView, (nextActiveView) => {
const _nextActiveView = nextActiveView as GridType | FormType | KanbanType
const _nextActiveView = nextActiveView as ViewType
if (_nextActiveView && _nextActiveView.id) {
selected.value = [_nextActiveView.id]
@ -66,7 +66,7 @@ function validate(value?: string) {
return 'View name is required'
}
if (views.value.every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== value)) {
if (views.value.every((v1) => v1.title !== value)) {
return 'View name should be unique'
}
@ -134,14 +134,13 @@ const initSortable = (el: HTMLElement) => {
onMounted(() => menuRef && initSortable(menuRef.$el))
// todo: fix view type, alias is missing for some reason?
/** Navigate to view by changing url param */
function changeView(view: { id: string; alias?: string; title?: string; type: ViewTypes }) {
router.push({ params: { viewTitle: (view.alias ?? view.title) || '' } })
router.push({ params: { viewTitle: view.title || '' } })
}
/** Rename a view */
async function onRename(view: Record<string, any>) {
async function onRename(view: ViewType) {
const valid = validate(view.title)
if (valid !== true) {
@ -153,7 +152,7 @@ async function onRename(view: Record<string, any>) {
try {
// todo typing issues, order and id do not exist on all members of ViewTypes (Kanban, Gallery, Form, Grid)
await api.dbView.update(view.id, {
await api.dbView.update(view.id!, {
title: view.title,
order: view.order,
})

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

@ -3,7 +3,7 @@ import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'noc
import MenuTop from './MenuTop.vue'
import MenuBottom from './MenuBottom.vue'
import Toolbar from './toolbar/index.vue'
import { computed, inject, provide, ref, useApi, useRoute, useViews, watch } from '#imports'
import { computed, inject, provide, ref, useRoute, useViews, watch } from '#imports'
import { ActiveViewInj, MetaInj, RightSidebarInj, ViewListInj } from '~/context'
const meta = inject(MetaInj, ref())
@ -12,8 +12,6 @@ const activeView = inject(ActiveViewInj, ref())
const { views, loadViews } = useViews(meta)
const { api } = useApi()
const route = useRoute()
provide(ViewListInj, views)

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

@ -1,6 +1,5 @@
<script setup lang="ts">
import type { ColumnType, TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk'
import type { ColumnType, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import SmartsheetGrid from '../smartsheet/Grid.vue'
import { computed, inject, provide, useMetas, useProvideSmartsheetStore, watch, watchEffect } from '#imports'

4
packages/nc-gui-v2/components/webhook/Test.vue

@ -10,7 +10,7 @@ interface Props {
const { hook } = defineProps<Props>()
const { $state, $api, $e } = useNuxtApp()
const { $api } = useNuxtApp()
const toast = useToast()
@ -23,7 +23,7 @@ const activeKey = ref(0)
watch(
() => hook?.operation,
async (v) => {
async () => {
await loadSampleData()
},
)

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

@ -24,7 +24,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
(meta: Ref<TableType>, column?: Ref<ColumnType>) => {
const { sqlUi } = useProject()
const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const toast = useToast()
const idType = null
@ -196,6 +196,11 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
}
await $api.dbTableColumn.create(meta.value.id as string, formState.value)
/** if LTAR column then force reload related table meta */
if (formState.value.uidt === UITypes.LinkToAnotherRecord && meta.value.id !== formState.value.childId) {
getMeta(formState.value.childId, true).then(() => {})
}
toast.success('Column created')
}
onSuccess?.()

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

@ -1,15 +1,9 @@
<script lang="ts" setup>
import { breakpointsTailwind } from '@vueuse/core'
import { navigateTo } from '#app'
import { computed, useBreakpoints, useGlobal, useProject, useRoute } from '#imports'
/** get current breakpoints (for enabling sidebar) */
const breakpoints = useBreakpoints(breakpointsTailwind)
import { computed, useGlobal, useRoute } from '#imports'
const { signOut, signedIn, isLoading, user } = useGlobal()
const { project } = useProject()
const route = useRoute()
const email = computed(() => user.value?.email ?? '---')
@ -83,7 +77,7 @@ const logout = () => {
</template>
</a-layout-header>
<div class="w-full" style="height: calc(100% - var(--header-height))">
<div class="w-full overflow-hidden" style="height: calc(100% - var(--header-height))">
<slot />
</div>
</a-layout>

10
packages/nc-gui-v2/pages/index/user/index.vue

@ -1,13 +1,3 @@
<script setup lang="ts">
import { navigateTo, useNuxtApp, useRoute } from '#app'
import MdiAccountCog from '~icons/mdi/account-cog'
import MdiFolderOutline from '~icons/mdi/folder-outline'
const { $api } = useNuxtApp()
const route = useRoute()
</script>
<template>
<NuxtLayout>
<NuxtPage />

4
packages/nc-gui-v2/pages/index/user/index/index.vue

@ -6,7 +6,7 @@ import { reactive, ref, useApi } from '#imports'
import MaterialSymbolsWarning from '~icons/material-symbols/warning'
import MdiKeyChange from '~icons/mdi/key-change'
const { api, isLoading } = useApi()
const { api } = useApi()
const { t } = useI18n()
@ -34,7 +34,7 @@ const formRules = {
{ required: true, message: t('msg.error.signUpRules.passwdRequired') },
// Passwords match
{
validator: (_: unknown, v: string) => {
validator: (_: unknown, _v: string) => {
return new Promise((resolve, reject) => {
if (form.password === form.passwordRepeat) return resolve(true)
reject(new Error(t('msg.error.signUpRules.passwdMismatch')))

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

@ -21,12 +21,6 @@ const currentMenu = ref<string[]>(['addORImport'])
provide(TabMetaInj, activeTab)
function onEdit(targetKey: number, action: string) {
if (action !== 'add') {
closeTab(targetKey)
}
}
function openQuickImportDialog(type: string) {
quickImportDialog.value = true
importType.value = type

4
packages/nc-gui-v2/pages/project/index/[id].vue

@ -4,13 +4,13 @@ import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { ref } from 'vue'
import { useToast } from 'vue-toastification'
import { navigateTo, useNuxtApp, useRoute } from '#app'
import { navigateTo, useRoute } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { projectTitleValidator } from '~/utils/validation'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
import { nextTick, reactive, useSidebar } from '#imports'
const { api, isLoading } = useApi()
const { api } = useApi()
useSidebar({ hasSidebar: false })

6
packages/nc-gui-v2/pages/project/index/create.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { onMounted, onUpdated } from '@vue/runtime-core'
import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { nextTick, reactive, ref, useApi, useSidebar } from '#imports'
@ -8,10 +8,6 @@ import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { projectTitleValidator } from '~/utils/validation'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
const name = ref('')
const valid = ref(false)
const { $e } = useNuxtApp()
const { api, isLoading } = useApi()

2
packages/nc-gui-v2/pages/projects/index.vue

@ -56,8 +56,6 @@ const deleteProject = (project: ProjectType) => {
},
})
}
const visible = ref(true)
</script>
<template>

9
packages/nocodb-sdk/src/lib/Api.ts

@ -122,7 +122,7 @@ export interface TableType {
export interface ViewType {
id?: string;
title?: string;
title: string;
deleted?: boolean;
order?: number;
fk_model_id?: string;
@ -163,6 +163,7 @@ export interface TableReqType {
pinned?: boolean;
deleted?: boolean;
order?: number;
mm?: boolean;
columns?: ColumnType[];
}
@ -234,6 +235,8 @@ export interface ColumnType {
deleted?: boolean;
visible?: boolean;
order?: number;
system?: number | boolean;
meta?: any;
colOptions?:
| LinkToAnotherRecordType
| FormulaType
@ -1516,7 +1519,7 @@ export class Api<
*/
reorder: (
tableId: string,
data: { order?: string },
data: { order?: number },
params: RequestParams = {}
) =>
this.request<void, any>({
@ -1630,7 +1633,7 @@ export class Api<
update: (
viewId: string,
data: {
order?: string;
order?: number;
title?: string;
show_system_fields?: boolean;
lock_type?: 'collaborative' | 'locked' | 'personal';

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

@ -23,7 +23,7 @@ import extractProps from '../meta/helpers/extractProps';
const { v4: uuidv4 } = require('uuid');
export default class View implements ViewType {
id?: string;
title?: string;
title: string;
uuid?: string;
password?: string;
show: boolean;

17
scripts/sdk/swagger.json

@ -1334,7 +1334,7 @@
"type": "object",
"properties": {
"order": {
"type": "string"
"type": "number"
}
}
}
@ -1506,7 +1506,7 @@
"type": "object",
"properties": {
"order": {
"type": "string"
"type": "number"
},
"title": {
"type": "string"
@ -6011,7 +6011,8 @@
"type": "string"
},
"title": {
"type": "string"
"type": "string",
"required": true
},
"deleted": {
"type": "boolean"
@ -6233,6 +6234,9 @@
"order": {
"type": "number"
},
"mm": {
"type": "boolean"
},
"columns": {
"type": "array",
"items": {
@ -6579,6 +6583,13 @@
"order": {
"type": "number"
},
"system": {
"type": [
"number",
"boolean"
]
},
"meta": {},
"colOptions": {
"oneOf": [
{

Loading…
Cancel
Save