diff --git a/packages/nc-cli/package-lock.json b/packages/nc-cli/package-lock.json index 8aea25408c5..ae152254426 100644 --- a/packages/nc-cli/package-lock.json +++ b/packages/nc-cli/package-lock.json @@ -6410,9 +6410,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -20501,9 +20501,9 @@ "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, "for-in": { "version": "1.0.2", diff --git a/packages/nc-gui/components/account/UserList.vue b/packages/nc-gui/components/account/UserList.vue index 1a501e4c0dd..b73f89c4d87 100644 --- a/packages/nc-gui/components/account/UserList.vue +++ b/packages/nc-gui/components/account/UserList.vue @@ -2,7 +2,16 @@ import { OrgUserRoles } from 'nocodb-sdk' import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk' import type { User } from '#imports' -import { extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useDebounceFn, useNuxtApp } from '#imports' +import { + extractSdkResponseErrorMsg, + iconMap, + useApi, + useCopy, + useDashboard, + useDebounceFn, + useNuxtApp, + useUserSorts, +} from '#imports' const { api, isLoading } = useApi() @@ -19,8 +28,14 @@ const { user: loggedInUser } = useGlobal() const { copy } = useCopy() +const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Org') + const users = ref([]) +const sortedUsers = computed(() => { + return handleGetSortedData(users.value, sorts.value) as UserType[] +}) + const currentPage = ref(1) const currentLimit = ref(10) @@ -64,6 +79,7 @@ const loadUsers = useDebounceFn(async (page = currentPage.value, limit = current onMounted(() => { loadUsers() + loadSorts() }) const updateRole = async (userId: string, roles: string) => { @@ -73,6 +89,12 @@ const updateRole = async (userId: string, roles: string) => { } as OrgUserReqType) message.success(t('msg.success.roleUpdated')) + users.value.forEach((user) => { + if (user.id === userId) { + user.roles = roles + } + }) + $e('a:org-user:role-updated', { role: roles }) } catch (e: any) { message.error(await extractSdkResponseErrorMsg(e)) @@ -176,10 +198,21 @@ const openDeleteModal = (user: UserType) => {
-
- {{ $t('labels.email') }} +
+ + {{ $t('labels.email') }} + + +
+
+ + {{ $t('objects.role') }} + +
-
{{ $t('objects.role') }}
{{ $t('labels.action') }}
@@ -193,7 +226,7 @@ const openDeleteModal = (user: UserType) => {
+import { iconMap } from '#imports' +import type { UsersSortType } from '~/lib' + +const { field, direction, handleUserSort } = defineProps<{ + field: UsersSortType['field'] + direction: UsersSortType['direction'] + handleUserSort: Function +}>() + +const isOpen = ref(false) + +const sortUserBy = (direction?: UsersSortType['direction']) => { + handleUserSort({ + field, + direction, + }) + isOpen.value = false +} + + + + + diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue index 39d807774b6..e39344c91e6 100644 --- a/packages/nc-gui/components/cell/DatePicker.vue +++ b/packages/nc-gui/components/cell/DatePicker.vue @@ -104,7 +104,7 @@ const placeholder = computed(() => { if (isEditColumn.value && (modelValue === '' || modelValue === null)) { return t('labels.optional') } else if (modelValue === null && showNull.value) { - return t('general.null') + return t('general.null').toUpperCase() } else if (isDateInvalid.value) { return t('msg.invalidDate') } else { @@ -243,7 +243,7 @@ const clickHandler = () => { :picker="picker" :tabindex="0" :bordered="false" - class="!w-full !py-1 !border-none" + class="!w-full !py-1 !border-none !text-current" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :format="dateFormat" :placeholder="placeholder" @@ -260,7 +260,7 @@ const clickHandler = () => { diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 321b42adaec..8a350bb0e17 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -157,7 +157,7 @@ const placeholder = computed(() => { if (isEditColumn.value && (modelValue === '' || modelValue === null)) { return t('labels.optional') } else if (modelValue === null && showNull.value) { - return t('general.null') + return t('general.null').toUpperCase() } else if (isDateInvalid.value) { return t('msg.invalidDate') } else { @@ -253,6 +253,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { const cellClickHook = inject(CellClickHookInj, null) const cellClickHandler = () => { + if (readOnly.value) return open.value = (active.value || editable.value) && !open.value } @@ -296,7 +297,7 @@ const isColDisabled = computed(() => { :disabled="isColDisabled" :show-time="true" :bordered="false" - class="!w-full !py-1 !border-none" + class="!w-full !py-1 !border-none !text-current" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :format="dateTimeFormat" :placeholder="placeholder" @@ -313,7 +314,7 @@ const isColDisabled = computed(() => { diff --git a/packages/nc-gui/components/cell/Decimal.vue b/packages/nc-gui/components/cell/Decimal.vue index aad130039ff..9e02452f048 100644 --- a/packages/nc-gui/components/cell/Decimal.vue +++ b/packages/nc-gui/components/cell/Decimal.vue @@ -112,7 +112,7 @@ watch(isExpandedFormOpen, () => { @selectstart.capture.stop @mousedown.stop /> - {{ $t('general.null') }} + {{ $t('general.null') }} {{ displayValue }} diff --git a/packages/nc-gui/components/cell/Duration.vue b/packages/nc-gui/components/cell/Duration.vue index b4bf77263cf..cde8bdf2ee0 100644 --- a/packages/nc-gui/components/cell/Duration.vue +++ b/packages/nc-gui/components/cell/Duration.vue @@ -111,7 +111,7 @@ const focus: VNodeRef = (el) => @mousedown.stop /> - {{ $t('general.null') }} + {{ $t('general.null') }} {{ localState }} diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue index 08fe8192e9a..0295a8ef6f2 100644 --- a/packages/nc-gui/components/cell/MultiSelect.vue +++ b/packages/nc-gui/components/cell/MultiSelect.vue @@ -567,7 +567,7 @@ const onFocus = () => { } :deep(.ant-select-selector) { - @apply !px-0; + @apply !pl-0; } :deep(.ant-select-selection-search-input) { diff --git a/packages/nc-gui/components/cell/Percent.vue b/packages/nc-gui/components/cell/Percent.vue index 94acc9186a1..3ae6d120fda 100644 --- a/packages/nc-gui/components/cell/Percent.vue +++ b/packages/nc-gui/components/cell/Percent.vue @@ -143,7 +143,7 @@ const onTabPress = (e: KeyboardEvent) => { @selectstart.capture.stop @mousedown.stop /> - {{ $t('general.null') }} + {{ $t('general.null') }}
+import { ActiveCellInj, EditModeInj, ReadonlyInj, provide, ref } from '#imports' + +interface Props { + modelValue?: string | null +} + +defineProps() + +provide(ReadonlyInj, ref(true)) + +provide(EditModeInj, ref(true)) + +provide(ActiveCellInj, ref(true)) + + + diff --git a/packages/nc-gui/components/cell/ReadOnlyUser.vue b/packages/nc-gui/components/cell/ReadOnlyUser.vue new file mode 100644 index 00000000000..0330bb97bf1 --- /dev/null +++ b/packages/nc-gui/components/cell/ReadOnlyUser.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue index 708b59d4c0d..8ba832c2674 100644 --- a/packages/nc-gui/components/cell/SingleSelect.vue +++ b/packages/nc-gui/components/cell/SingleSelect.vue @@ -322,7 +322,8 @@ const onFocus = () => { :disabled="readOnly || !editAllowed" :show-search="!isMobileMode && isOpen && active" :show-arrow="hasEditRoles && !readOnly && active && (vModel === null || vModel === undefined)" - :dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`" + :dropdown-class-name="`nc-dropdown-single-select-cell !min-w-200px ${isOpen && active ? 'active' : ''}`" + :dropdown-match-select-width="true" @select="onSelect" @keydown="onKeydown($event)" @search="search" @@ -399,7 +400,12 @@ const onFocus = () => { } :deep(.ant-select-selector) { - @apply !px-0; + @apply !pl-0 !pr-4; +} + +:deep(.ant-select-selector .ant-select-selection-item) { + @apply flex items-center; + text-overflow: clip; } :deep(.ant-select-selection-search-input) { diff --git a/packages/nc-gui/components/cell/TimePicker.vue b/packages/nc-gui/components/cell/TimePicker.vue index e61297f53f6..34081a1e1e3 100644 --- a/packages/nc-gui/components/cell/TimePicker.vue +++ b/packages/nc-gui/components/cell/TimePicker.vue @@ -96,7 +96,7 @@ const placeholder = computed(() => { if (isEditColumn.value && (modelValue === '' || modelValue === null)) { return t('labels.optional') } else if (modelValue === null && showNull.value) { - return t('general.null') + return t('general.null').toUpperCase() } else if (isTimeInvalid.value) { return t('msg.invalidTime') } else { @@ -133,7 +133,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { :bordered="false" use12-hours format="HH:mm" - class="!w-full !py-1 !border-none" + class="!w-full !py-1 !border-none !text-current" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :placeholder="placeholder" :allow-clear="!readOnly && !localState && !isPk" @@ -148,7 +148,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { diff --git a/packages/nc-gui/components/cell/Url.vue b/packages/nc-gui/components/cell/Url.vue index b624417e93f..8aa39bac320 100644 --- a/packages/nc-gui/components/cell/Url.vue +++ b/packages/nc-gui/components/cell/Url.vue @@ -108,7 +108,7 @@ watch( @mousedown.stop /> - $t('general.null') + {{ $t('general.null') }} { - const label = item?.display_name || item?.email - if (label) { - acc.push({ - label, - value: item.id, - }) - } - return acc - }, [] as { label: string; value: string }[]) || [] + selected = modelValue + ? (Array.isArray(modelValue) ? modelValue : [modelValue]).reduce((acc, item) => { + const label = item?.display_name || item?.email + if (label) { + acc.push({ + label, + value: item.id, + }) + } + return acc + }, [] as { label: string; value: string }[]) + : [] } return selected @@ -280,7 +281,7 @@ const filterOption = (input: string, option: any) => { }" > @@ -310,7 +325,7 @@ const filterOption = (input: string, option: any) => { :open="isOpen && editAllowed" :disabled="readOnly || !editAllowed" :class="{ 'caret-transparent': !hasEditRoles }" - :dropdown-class-name="`nc-dropdown-user-select-cell ${isOpen ? 'active' : ''}`" + :dropdown-class-name="`nc-dropdown-user-select-cell !min-w-200px ${isOpen ? 'active' : ''}`" :filter-option="filterOption" @search="search" @keydown.stop @@ -326,7 +341,7 @@ const filterOption = (input: string, option: any) => { :class="`nc-select-option-${column.title}-${op.email}`" @click.stop > - + - {{ op.display_name?.length ? op.display_name : op.email }} + + + + + {{ op.display_name?.length ? op.display_name : op.email }} + + @@ -433,7 +464,7 @@ const filterOption = (input: string, option: any) => { } :deep(.ant-select-selector) { - @apply !px-0; + @apply !pl-0; } :deep(.ant-select-selection-search-input) { diff --git a/packages/nc-gui/components/cell/YearPicker.vue b/packages/nc-gui/components/cell/YearPicker.vue index 69e33029121..090845cb17b 100644 --- a/packages/nc-gui/components/cell/YearPicker.vue +++ b/packages/nc-gui/components/cell/YearPicker.vue @@ -83,7 +83,7 @@ const placeholder = computed(() => { if (isEditColumn.value && (modelValue === '' || modelValue === null)) { return t('labels.optional') } else if (modelValue === null && showNull.value) { - return t('general.null') + return t('general.null').toUpperCase() } else if (isYearInvalid.value) { return t('msg.invalidTime') } else { @@ -119,7 +119,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { :tabindex="0" picker="year" :bordered="false" - class="!w-full !py-1 !border-none" + class="!w-full !py-1 !border-none !text-current" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :placeholder="placeholder" :allow-clear="(!readOnly && !localState && !isPk) || isEditColumn" @@ -136,7 +136,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { diff --git a/packages/nc-gui/components/erd/utils.ts b/packages/nc-gui/components/erd/utils.ts index 078c4b3fbf3..97f41821850 100644 --- a/packages/nc-gui/components/erd/utils.ts +++ b/packages/nc-gui/components/erd/utils.ts @@ -175,9 +175,10 @@ export function useErdElements(tables: MaybeRef, props: MaybeRef config.value.showAllColumns || (!config.value.showAllColumns && isLinksOrLTAR(col)), - ) || [] + metasWithIdAsKey.value[table.id].columns?.filter((col) => { + if ([UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt as UITypes) && col.system) return false + return config.value.showAllColumns || (!config.value.showAllColumns && isLinksOrLTAR(col)) + }) || [] const pkAndFkColumns = columns .filter(() => config.value.showPkAndFk) diff --git a/packages/nc-gui/components/project/AccessSettings.vue b/packages/nc-gui/components/project/AccessSettings.vue index 4a8f7ac0621..cfcd2b44e12 100644 --- a/packages/nc-gui/components/project/AccessSettings.vue +++ b/packages/nc-gui/components/project/AccessSettings.vue @@ -8,8 +8,9 @@ import { parseStringDateTime, timeAgo, } from 'nocodb-sdk' -import type { WorkspaceUserRoles } from 'nocodb-sdk' -import { isEeUI, storeToRefs } from '#imports' +import type { Roles, WorkspaceUserRoles } from 'nocodb-sdk' +import { isEeUI, storeToRefs, useUserSorts } from '#imports' +import type { User } from '#imports' const basesStore = useBases() const { getBaseUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore @@ -17,6 +18,8 @@ const { activeProjectId } = storeToRefs(basesStore) const { orgRoles, baseRoles } = useRoles() +const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Project') + const isSuper = computed(() => orgRoles.value?.[OrgUserRoles.SUPER_ADMIN]) interface Collaborators { @@ -24,6 +27,7 @@ interface Collaborators { email: string main_roles: OrgUserRoles roles: ProjectRoles + base_roles: Roles workspace_roles: WorkspaceUserRoles created_at: string } @@ -35,6 +39,14 @@ const isLoading = ref(false) const isSearching = ref(false) const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>([]) +const filteredCollaborators = computed(() => + collaborators.value.filter((collab) => collab.email.toLowerCase().includes(userSearchText.value.toLowerCase())), +) + +const sortedCollaborators = computed(() => { + return handleGetSortedData(filteredCollaborators.value, sorts.value) +}) + const loadCollaborators = async () => { try { const { users, totalRows } = await getBaseUsers({ @@ -64,29 +76,33 @@ const loadCollaborators = async () => { } const updateCollaborator = async (collab: any, roles: ProjectRoles) => { + const currentCollaborator = collaborators.value.find((coll) => coll.id === collab.id)! + try { if ( !roles || (roles === ProjectRoles.NO_ACCESS && !isEeUI) || - (collab.workspace_roles && WorkspaceRolesToProjectRoles[collab.workspace_roles as WorkspaceUserRoles] === roles && isEeUI) + (currentCollaborator.workspace_roles && + WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] === roles && + isEeUI) ) { - await removeProjectUser(activeProjectId.value!, collab) + await removeProjectUser(activeProjectId.value!, currentCollaborator as unknown as User) if ( - collab.workspace_roles && - WorkspaceRolesToProjectRoles[collab.workspace_roles as WorkspaceUserRoles] === roles && + currentCollaborator.workspace_roles && + WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] === roles && isEeUI ) { - collab.roles = WorkspaceRolesToProjectRoles[collab.workspace_roles as WorkspaceUserRoles] + currentCollaborator.roles = WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] } else { - collab.roles = ProjectRoles.NO_ACCESS + currentCollaborator.roles = ProjectRoles.NO_ACCESS } - } else if (collab.base_roles) { - collab.roles = roles - await updateProjectUser(activeProjectId.value!, collab) + } else if (currentCollaborator.base_roles) { + currentCollaborator.roles = roles + await updateProjectUser(activeProjectId.value!, currentCollaborator as unknown as User) } else { - collab.roles = roles - collab.base_roles = roles - await createProjectUser(activeProjectId.value!, collab) + currentCollaborator.roles = roles + currentCollaborator.base_roles = roles + await createProjectUser(activeProjectId.value!, currentCollaborator as unknown as User) } } catch (e: any) { message.error(await extractSdkResponseErrorMsg(e)) @@ -106,16 +122,13 @@ onMounted(async () => { } else if (currentRoleIndex !== -1) { accessibleRoles.value = OrderedProjectRoles.slice(currentRoleIndex + 1) } + loadSorts() } catch (e: any) { message.error(await extractSdkResponseErrorMsg(e)) } finally { isLoading.value = false } }) - -const filteredCollaborators = computed(() => - collaborators.value.filter((collab) => collab.email.toLowerCase().includes(userSearchText.value.toLowerCase())), -)
diff --git a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue index 8e03ad97469..2fdc6c66a45 100644 --- a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue +++ b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue @@ -79,7 +79,16 @@ const mounted = ref(false) const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber] -const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup, UITypes.Links] +const onlyNameUpdateOnEditColumns = [ + UITypes.LinkToAnotherRecord, + UITypes.Lookup, + UITypes.Rollup, + UITypes.Links, + UITypes.CreatedTime, + UITypes.LastModifiedTime, + UITypes.CreatedBy, + UITypes.LastModifiedBy, +] // To close column type dropdown on escape and // close modal only when the type popup is close @@ -344,6 +353,7 @@ if (props.fromTableExplorer) {
diff --git a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue index bb9191caf25..d4e3531d271 100644 --- a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue @@ -5,6 +5,7 @@ import jsep from 'jsep' import { FormulaError, UITypes, + isCreatedOrLastModifiedByCol, jsepCurlyHook, substituteColumnIdWithAliasInFormula, validateFormulaAndExtractTreeWithType, @@ -51,7 +52,18 @@ const { predictFunction: _predictFunction } = useNocoEe() const meta = inject(MetaInj, ref()) const supportedColumns = computed( - () => meta?.value?.columns?.filter((col) => !uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) || [], + () => + meta?.value?.columns?.filter((col) => { + if (uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) { + return false + } + + if (isCreatedOrLastModifiedByCol(col) && col.system) { + return false + } + + return true + }) || [], ) const { getMeta } = useMetas() diff --git a/packages/nc-gui/components/smartsheet/column/LookupOptions.vue b/packages/nc-gui/components/smartsheet/column/LookupOptions.vue index 6c08af0cc0b..01cdb90ca4e 100644 --- a/packages/nc-gui/components/smartsheet/column/LookupOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/LookupOptions.vue @@ -22,7 +22,7 @@ const baseStore = useBase() const { tables } = storeToRefs(baseStore) -const { metas } = useMetas() +const { metas, getMeta } = useMetas() setAdditionalValidations({ fk_relation_column_id: [{ required: true, message: t('general.required') }], @@ -48,12 +48,15 @@ const refTables = computed(() => { return _refTables as Required }>[] }) +const selectedTable = computed(() => { + return refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id) +}) + const columns = computed(() => { - const selectedTable = refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id) - if (!selectedTable?.id) { + if (!selectedTable.value?.id) { return [] } - return metas.value[selectedTable.id].columns.filter( + return metas.value[selectedTable.value.id]?.columns.filter( (c: ColumnType) => vModel.value.fk_lookup_column_id === c.id || (!isSystemColumn(c) && c.id !== vModel.value.id && c.uidt !== UITypes.Links), ) @@ -66,7 +69,10 @@ onMounted(() => { } }) -const onRelationColChange = () => { +const onRelationColChange = async () => { + if (selectedTable.value) { + await getMeta(selectedTable.value.id) + } vModel.value.fk_lookup_column_id = columns.value?.[0]?.id onDataTypeChange() } diff --git a/packages/nc-gui/components/smartsheet/column/RollupOptions.vue b/packages/nc-gui/components/smartsheet/column/RollupOptions.vue index f1170621f9b..4681f03bd07 100644 --- a/packages/nc-gui/components/smartsheet/column/RollupOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/RollupOptions.vue @@ -35,7 +35,7 @@ const baseStore = useBase() const { tables } = storeToRefs(baseStore) -const { metas } = useMetas() +const { metas, getMeta } = useMetas() const { t } = useI18n() @@ -70,14 +70,16 @@ const refTables = computed(() => { return _refTables as Required }>[] }) -const columns = computed(() => { - const selectedTable = refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id) +const selectedTable = computed(() => { + return refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id) +}) - if (!selectedTable?.id) { +const columns = computed(() => { + if (!selectedTable.value?.id) { return [] } - return metas.value[selectedTable.id].columns.filter( + return metas.value[selectedTable.value.id]?.columns.filter( (c: ColumnType) => !isVirtualCol(c.uidt as UITypes) && (!isSystemColumn(c) || c.pk), ) }) @@ -90,7 +92,10 @@ onMounted(() => { } }) -const onRelationColChange = () => { +const onRelationColChange = async () => { + if (selectedTable.value) { + await getMeta(selectedTable.value.id) + } vModel.value.fk_rollup_column_id = columns.value?.[0]?.id onDataTypeChange() } diff --git a/packages/nc-gui/components/smartsheet/column/SelectOptions.vue b/packages/nc-gui/components/smartsheet/column/SelectOptions.vue index 0df0ed2c744..5f59c6be833 100644 --- a/packages/nc-gui/components/smartsheet/column/SelectOptions.vue +++ b/packages/nc-gui/components/smartsheet/column/SelectOptions.vue @@ -15,6 +15,7 @@ interface Option { const props = defineProps<{ value: any + fromTableExplorer?: boolean }>() const emit = defineEmits(['update:value']) @@ -308,7 +309,7 @@ const loadListData = async ($state: any) => { ref="optionsWrapperDomRef" class="nc-col-option-select-option overflow-x-auto scrollbar-thin-dull" :style="{ - maxHeight: 'calc(min(30vh, 250px))', + maxHeight: props.fromTableExplorer ? 'calc(100vh - (var(--topbar-height) * 3.6) - 320px)' : 'calc(min(30vh, 250px))', }" > diff --git a/packages/nc-gui/components/smartsheet/details/Fields.vue b/packages/nc-gui/components/smartsheet/details/Fields.vue index e9c5ade692e..7f32a191974 100644 --- a/packages/nc-gui/components/smartsheet/details/Fields.vue +++ b/packages/nc-gui/components/smartsheet/details/Fields.vue @@ -100,6 +100,7 @@ const fields = computed({ const x = ((meta.value?.columns as ColumnType[]) ?? []) .filter((field) => !field.fk_column_id && !isSystemColumn(field)) .concat(newFields.value) + .map((field) => updateDefaultColumnValues(field)) .sort((a, b) => { return getFieldOrder(a) - getFieldOrder(b) }) @@ -268,8 +269,30 @@ const duplicateField = async (field: TableExplorerColumn) => { const onFieldUpdate = (state: TableExplorerColumn) => { const col = fields.value.find((col) => compareCols(col, state)) if (!col) return + const diffs = diff(col, state) - if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs)) { + + // hack to prevent update status `Updated Field` when clicking on field first time + let isUpdated = true + + if ( + [UITypes.SingleSelect, UITypes.MultiSelect].includes(col.uidt) && + Object.keys(diffs).length === 1 && + diffs?.colOptions?.options && + (diffs?.colOptions?.options?.length === 0 || + (diffs?.colOptions?.options[0]?.index !== undefined && Object.keys(diffs?.colOptions?.options[0] || {}).length === 1)) + ) { + isUpdated = false + } + + if (!isUpdated) { + let field = fields.value.find((field) => compareCols(field, state)) + if (field) { + field = state + } + } + + if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs) || !isUpdated) { ops.value = ops.value.filter((op) => op.op === 'add' || !compareCols(op.column, state)) } else { const field = ops.value.find((op) => compareCols(op.column, state)) @@ -291,7 +314,7 @@ const onFieldUpdate = (state: TableExplorerColumn) => { return } - if (field && !moveField) { + if (field || (field && moveField)) { field.column = state } else { ops.value.push({ @@ -376,6 +399,19 @@ const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => { return } + const mop = moveOps.value.find((op) => compareCols(op.column, fields.value[_event.moved.oldIndex])) + if (mop) { + mop.index = _event.moved.newIndex + mop.order = order + } else { + moveOps.value.push({ + op: 'move', + column: fields.value[_event.moved.oldIndex], + index: _event.moved.newIndex, + order, + }) + } + if (op) { onFieldUpdate({ ...op.column, @@ -393,19 +429,6 @@ const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => { }, }) } - - const mop = moveOps.value.find((op) => compareCols(op.column, fields.value[_event.moved.oldIndex])) - if (mop) { - mop.index = _event.moved.newIndex - mop.order = order - } else { - moveOps.value.push({ - op: 'move', - column: fields.value[_event.moved.oldIndex], - index: _event.moved.newIndex, - order, - }) - } } const isColumnValid = (column: TableExplorerColumn) => { @@ -438,6 +461,52 @@ const isColumnValid = (column: TableExplorerColumn) => { return true } +function updateDefaultColumnValues(column: TableExplorerColumn) { + if (column.uidt === UITypes.QrCode && column.colOptions?.fk_qr_value_column_id) { + if (!column?.fk_qr_value_column_id) { + column.fk_qr_value_column_id = column.colOptions.fk_qr_value_column_id + } + } + + if (column.uidt === UITypes.Barcode && column.colOptions?.fk_barcode_value_column_id) { + if (!column?.fk_barcode_value_column_id) { + column.fk_barcode_value_column_id = column.colOptions.fk_barcode_value_column_id + } + } + + if (column.uidt === UITypes.Lookup && column?.colOptions?.fk_lookup_column_id && column?.colOptions?.fk_relation_column_id) { + if (!column?.fk_lookup_column_id) { + column.fk_lookup_column_id = column.colOptions.fk_lookup_column_id + } + if (!column?.fk_relation_column_id) { + column.fk_relation_column_id = column.colOptions.fk_relation_column_id + } + } + + if ( + column.uidt === UITypes.Rollup && + column?.colOptions?.fk_relation_column_id && + column?.colOptions?.fk_rollup_column_id && + column?.colOptions?.rollup_function + ) { + if (!column?.fk_relation_column_id) { + column.fk_relation_column_id = column.colOptions.fk_relation_column_id + } + if (!column?.fk_rollup_column_id) { + column.fk_rollup_column_id = column.colOptions.fk_rollup_column_id + } + if (!column?.rollup_function) { + column.rollup_function = column.colOptions.rollup_function + } + } + + if (column.uidt === UITypes.Formula && column.colOptions?.formula_raw && !column?.formula_raw) { + column.formula_raw = column.colOptions?.formula_raw + } + + return column +} + const recoverField = (state: TableExplorerColumn) => { const field = ops.value.find((op) => compareCols(op.column, state)) if (field) { @@ -512,10 +581,13 @@ const saveChanges = async () => { view_id: view.value?.id as string, } } - } - for (const f of fields.value) { - console.log(f.title, getFieldOrder(f)) + if (op && op.op === 'update') { + op.column.column_order = { + order: mop.order, + view_id: view.value?.id as string, + } + } } for (const op of ops.value) { @@ -545,7 +617,10 @@ const saveChanges = async () => { await loadViewColumns() if (res) { - ops.value = (res.failedOps as op[]) || [] + ops.value = + res.failedOps && res.failedOps?.length + ? (res.failedOps as (op & { error: unknown })[]).map(({ error: _, ...rest }) => rest) + : [] newFields.value = newFields.value.filter((col) => { if (res.failedOps) { const op = res.failedOps.find((fop) => { @@ -631,8 +706,14 @@ onKeyDown('ArrowUp', () => { onKeyDown('Delete', () => { if (isLocked.value) return - if (document.activeElement?.tagName === 'INPUT') return - if (document.activeElement?.tagName === 'TEXTAREA') return + if ( + document.activeElement?.tagName === 'INPUT' || + document.activeElement?.tagName === 'TEXTAREA' || + // A rich text editor is a div with the contenteditable attribute set to true. + document.activeElement?.getAttribute('contenteditable') + ) { + return + } const isDeletedField = fieldStatus(activeField.value) === 'delete' if (!isDeletedField && activeField.value) { @@ -643,8 +724,14 @@ onKeyDown('Delete', () => { onKeyDown('Backspace', () => { if (isLocked.value) return - if (document.activeElement?.tagName === 'INPUT') return - if (document.activeElement?.tagName === 'TEXTAREA') return + if ( + document.activeElement?.tagName === 'INPUT' || + document.activeElement?.tagName === 'TEXTAREA' || + // A rich text editor is a div with the contenteditable attribute set to true. + document.activeElement?.getAttribute('contenteditable') + ) { + return + } const isDeletedField = fieldStatus(activeField.value) === 'delete' if (!isDeletedField && activeField.value) { @@ -705,6 +792,16 @@ const onFieldOptionUpdate = () => { isFieldIdCopied.value = false }, 200) } + +watch( + fields, + () => { + if (activeField.value) { + activeField.value = fields.value.find((field) => field.id === activeField.value.id) || activeField.value + } + }, + { deep: true }, +)