feat: data reflection preps (#10227)

* feat: integration hooks

* feat: data reflection

* feat: improved UX for data reflection

* chore: lint

* fix(nc-gui): update nocodb integration ui

* fix(nocodb): type error

* fix(nc-gui): nocodb integration icon and modal gap issue

* fix: defer integration hooks

* fix: check proper state

* refactor(nc-gui): integration modal

* refactor(nc-gui): integration modal ui changes

* refactor: change default port

* fix(nc-gui): add base id copy input

* fix(nc-gui): schema dropdown placement and item height issue

* fix(nc-gui): nocodb connection bg color issue

* fix(nc-gui): update nocodb integration count and user logo

* fix: rspack keep class

* feat: get connection menu item

* chore: rebase issue

* fix: hide nc from sources

* feat: move data reflection to model level

* fix: remove deprecated fn & fix type errors

* feat: reflection settings

* feat: feature flag for data reflection

* refactor: avoid save on feature flags

* fix: properly show host

* fix: PR requested changes

* fix: use named parameters for queries

---------

Co-authored-by: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com>
This commit is contained in:
Mert E.
2025-01-14 14:59:45 +03:00
committed by GitHub
parent 51edd98b9e
commit c2f50efbb9
39 changed files with 1379 additions and 272 deletions

View File

@@ -26,6 +26,8 @@ const { allCollaborators } = storeToRefs(useWorkspace())
const { bases } = storeToRefs(useBases())
const { isFeatureEnabled } = useBetaFeatureToggle()
const isDeleteIntegrationModalOpen = ref(false)
const toBeDeletedIntegration = ref<
| (IntegrationType & {
@@ -142,6 +144,14 @@ const openDeleteIntegration = async (source: IntegrationType) => {
isLoadingGetLinkedSources.value = false
}
const openEditIntegration = (integration: IntegrationType) => {
if (!isFeatureEnabled(FEATURE_FLAG.DATA_REFLECTION) && integration.sub_type === SyncDataType.NOCODB) {
return
}
editIntegration(integration)
}
const onDeleteConfirm = async () => {
const isDeleted = await deleteIntegration(toBeDeletedIntegration.value, true)
@@ -308,6 +318,7 @@ onKeyStroke('ArrowDown', onDown)
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-type !hover:bg-gray-100 select-none cursor-pointer"
:class="{
@@ -401,7 +412,7 @@ onKeyStroke('ArrowDown', onDown)
<template v-if="filteredIntegrations?.length">
<table class="h-full max-h-[calc(100%_-_55px)] w-full">
<tbody>
<tr v-for="integration of filteredIntegrations" :key="integration.id" @click="editIntegration(integration)">
<tr v-for="integration of filteredIntegrations" :key="integration.id" @click="openEditIntegration(integration)">
<td class="cell-title">
<div
class="gap-3"
@@ -420,22 +431,20 @@ onKeyStroke('ArrowDown', onDown)
</span>
</div>
</td>
<td class="cell-type">
<div>
<NcBadge rounded="lg" class="flex items-center gap-2 px-0 py-1 !h-7 truncate !border-transparent">
<GeneralIntegrationIcon :type="integration.sub_type" />
<NcTooltip placement="bottom" show-on-truncate-only class="text-sm truncate">
<template #title> {{ clientTypesMap[integration?.sub_type]?.text || integration?.sub_type }}</template>
<NcTooltip placement="bottom" class="h-8 w-8 flex-none flex items-center justify-center children:flex-none">
<template #title> {{ clientTypesMap[integration?.sub_type]?.text || integration?.sub_type }}</template>
{{
integration.sub_type && clientTypesMap[integration.sub_type]
? clientTypesMap[integration.sub_type]?.text
: integration.sub_type
}}
</NcTooltip>
</NcBadge>
<GeneralIntegrationIcon
:type="integration.sub_type"
:size="integration.sub_type === SyncDataType.NOCODB ? 'xxl' : 'lg'"
/>
</NcTooltip>
</div>
</td>
<td class="cell-created-date">
<div>
<NcTooltip placement="bottom" show-on-truncate-only>
@@ -447,7 +456,13 @@ onKeyStroke('ArrowDown', onDown)
</td>
<td class="cell-added-by">
<div>
<NcTooltip :disabled="!isUserDeleted(integration.created_by)" class="w-full">
<div v-if="integration.sub_type === SyncDataType.NOCODB" class="flex items-center gap-3">
<div class="h-8 w-8 grid place-items-center">
<GeneralIcon icon="nocodb1" />
</div>
<div class="text-sm !leading-5 capitalize font-semibold truncate">NocoDB Cloud</div>
</div>
<NcTooltip v-else :disabled="!isUserDeleted(integration.created_by)" class="w-full">
<template #title>
{{ `User not part of this ${isEeUI ? 'workspace' : 'organisation'} anymore` }}
</template>
@@ -524,11 +539,16 @@ onKeyStroke('ArrowDown', onDown)
v-if="integration.type && integrationCategoryNeedDefault(integration.type) && !integration.is_default"
@click="setDefaultIntegration(integration)"
>
<GeneralIcon class="text-gray-800" icon="star" />
<GeneralIcon class="text-current opacity-80" icon="star" />
<span>Set as default</span>
</NcMenuItem>
<NcMenuItem @click="editIntegration(integration)">
<GeneralIcon class="text-gray-800" icon="edit" />
<NcMenuItem
:disabled="
!isFeatureEnabled(FEATURE_FLAG.DATA_REFLECTION) && integration.sub_type === SyncDataType.NOCODB
"
@click="openEditIntegration(integration)"
>
<GeneralIcon class="text-current opacity-80" icon="edit" />
<span>{{ $t('general.edit') }}</span>
</NcMenuItem>
<NcTooltip :disabled="integration?.sub_type !== ClientType.SQLITE">
@@ -542,24 +562,22 @@ onKeyStroke('ArrowDown', onDown)
</template>
<NcMenuItem
:disabled="integration?.sub_type === ClientType.SQLITE"
:disabled="
integration?.sub_type === ClientType.SQLITE || integration?.sub_type === SyncDataType.NOCODB
"
@click="duplicateIntegration(integration)"
>
<GeneralIcon
:class="{
'text-current': integration?.sub_type === ClientType.SQLITE,
'text-gray-800': integration?.sub_type !== ClientType.SQLITE,
}"
icon="duplicate"
/>
<GeneralIcon class="text-current opacity-80" icon="duplicate" />
<span>{{ $t('general.duplicate') }}</span>
</NcMenuItem>
</NcTooltip>
<NcDivider />
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click="openDeleteIntegration(integration)">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</NcMenuItem>
<template v-if="integration?.sub_type !== SyncDataType.NOCODB">
<NcDivider />
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click="openDeleteIntegration(integration)">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</NcMenuItem>
</template>
</NcMenu>
</template>
</NcDropdown>
@@ -846,9 +864,9 @@ onKeyStroke('ArrowDown', onDown)
}
&.cell-type {
@apply basis-[20%];
@apply w-[120px];
& > div {
@apply min-w-[178px];
@apply min-w-[98px];
}
}

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup>
import { IntegrationCategoryType, SyncDataType } from '#imports'
const props = defineProps<{ loadDatasourceInfo?: boolean; baseId?: string }>()
const { loadDatasourceInfo, baseId } = toRefs(props)
@@ -44,7 +46,10 @@ const activeIntegrationType = computed(() => {
wrap-class-name="nc-modal-edit-or-add-integration"
@keydown.esc="isEditOrAddIntegrationModalOpen = false"
>
<div v-if="activeIntegrationType === IntegrationCategoryType.DATABASE" class="h-full">
<div
v-if="activeIntegrationType === IntegrationCategoryType.DATABASE && activeIntegration?.sub_type !== SyncDataType.NOCODB"
class="h-full"
>
<WorkspaceIntegrationsFormsEditOrAddDatabase
v-model:open="isEditOrAddIntegrationModalOpen"
:connection-type="activeIntegrationSubType"
@@ -52,11 +57,11 @@ const activeIntegrationType = computed(() => {
:base-id="baseId"
/>
</div>
<div v-else-if="activeIntegrationType === IntegrationCategoryType.AI" class="h-full">
<div v-else class="h-full">
<WorkspaceIntegrationsFormsEditOrAddCommon
v-model:open="isEditOrAddIntegrationModalOpen"
:integration-type="activeIntegrationItem?.type"
:integration-sub-type="activeIntegrationItem?.subType"
:integration-sub-type="activeIntegrationItem?.sub_type"
:load-datasource-info="loadDatasourceInfo"
:base-id="baseId"
/>

View File

@@ -2,7 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core'
import { IntegrationCategoryType } from 'nocodb-sdk'
import NcModal from '~/components/nc/Modal.vue'
/* eslint-disable @typescript-eslint/consistent-type-imports */
import { type IntegrationItemType, SyncDataType } from '#imports'
const props = withDefaults(
@@ -30,6 +30,8 @@ const { syncDataUpvotes, updateSyncDataUpvotes } = useGlobal()
const { isFeatureEnabled } = useBetaFeatureToggle()
const { activeWorkspace } = storeToRefs(useWorkspace())
const easterEggToggle = computed(() => isFeatureEnabled(FEATURE_FLAG.INTEGRATIONS))
const router = useRouter()
@@ -166,6 +168,7 @@ const integrationsMapByCategory = computed(() => {
list: IntegrationItemType[]
isAvailable?: boolean
teleEventName?: IntegrationCategoryType
value: IntegrationCategoryType
}
>,
)
@@ -202,7 +205,11 @@ const handleUpvote = (category: IntegrationCategoryType, syncDataType: SyncDataT
const handleAddIntegration = async (category: IntegrationCategoryType, integration: IntegrationItemType) => {
if (!integration.isAvailable) {
handleUpvote(category, integration.subType)
handleUpvote(category, integration.sub_type)
return
}
if (!isFeatureEnabled(FEATURE_FLAG.DATA_REFLECTION) && integration.sub_type === SyncDataType.NOCODB) {
return
}
@@ -229,6 +236,10 @@ onMounted(() => {
}
})
const dataReflectionEnabled = computed(() => {
return !!activeWorkspace.value?.data_reflection_enabled
})
watch(activeViewTab, (value) => {
if (value !== 'integrations' && isOpenFilter.value) {
isOpenFilter.value = false
@@ -386,7 +397,7 @@ watch(activeViewTab, (value) => {
>
</div>
<div v-if="category.list.length" class="integration-type-list">
<template v-for="integration of category.list" :key="integration.subType">
<template v-for="integration of category.list" :key="integration.sub_type">
<NcTooltip
v-if="easterEggToggle || integration.isAvailable"
:disabled="integration?.isAvailable"
@@ -409,14 +420,25 @@ watch(activeViewTab, (value) => {
<div class="name">{{ $t(integration.title) }}</div>
<div v-if="integration.subtitle" class="subtitle flex-1">{{ $t(integration.subtitle) }}</div>
</div>
<div v-if="integration?.isAvailable" class="action-btn">+</div>
<div
v-if="
!isFeatureEnabled(FEATURE_FLAG.DATA_REFLECTION) && integration?.sub_type === SyncDataType.NOCODB
"
></div>
<div v-else-if="integration?.sub_type === SyncDataType.NOCODB" class="flex items-center">
<template v-if="dataReflectionEnabled">
<GeneralIcon icon="check" class="text-primary text-lg" />
</template>
<div v-else class="action-btn !block">+</div>
</div>
<div v-else-if="integration?.isAvailable" class="action-btn">+</div>
<div v-else class="">
<NcButton
type="secondary"
size="xs"
class="integration-upvote-btn !rounded-lg !px-1 !py-0"
:class="{
selected: upvotesData.has(integration.subType),
selected: upvotesData.has(integration.sub_type),
}"
>
<div class="flex items-center gap-2">
@@ -572,7 +594,7 @@ watch(activeViewTab, (value) => {
}
.action-btn {
@apply hidden text-2xl text-gray-500 w-7 h-7 text-center;
@apply hidden text-2xl text-gray-500 w-7 h-7 text-center flex-none;
}
&.is-available {

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
const props = defineProps<{
modelValue: string
password?: boolean
}>()
const { copy } = useCopy()
const { t } = useI18n()
const copied = ref(false)
const copyValue = async () => {
await copy(props.modelValue)
message.info(t('msg.info.copiedToClipboard'))
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
}
</script>
<template>
<div class="relative inline-flex items-center w-full h-full group" @click="copyValue">
<a-input :value="modelValue" disabled :type="password ? 'password' : 'input'" class="!pr-8 !truncate" />
<div
class="absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer transition-colors text-nc-content-gray-muted group-hover:text-nc-content-gray-subtle"
>
<GeneralIcon v-if="copied" class="max-h-4 min-w-4 !text-current" icon="check" />
<GeneralIcon v-else class="max-h-4 min-w-4 !text-current" icon="copy" />
</div>
</div>
</template>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,102 @@
<script lang="ts" setup>
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits(['update:modelValue'])
const vModel = useVModel(props, 'modelValue', emit)
const { basesList } = storeToRefs(useBases())
const isOpen = ref<boolean>(false)
const { copy } = useCopy()
const { t } = useI18n()
const copied = ref(false)
const copyValue = async () => {
await copy(vModel.value ?? '')
message.info(t('msg.info.copiedToClipboard'))
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
}
onMounted(() => {
if (basesList.value?.length && basesList.value[0]?.id) {
vModel.value = basesList.value[0].id
}
})
</script>
<template>
<div
class="relative group w-full border-1 border-nc-border-gray-medium rounded-lg bg-nc-bg-gray-extralight h-8 flex items-center text-nc-content-gray-muted"
>
<NcDropdown v-model:visible="isOpen" overlay-class-name="overflow-hidden max-w-[320px]">
<div class="h-full flex-1 px-3 mr-8 flex items-center gap-2 cursor-pointer" @click.stop>
<div>
{{ vModel }}
</div>
<GeneralIcon
icon="chevronDown"
class="!text-current opacity-70 flex-none transform transition-transform duration-250 w-3.5 h-3.5"
:class="{ '!rotate-180': isOpen }"
/>
</div>
<template #overlay>
<NcList
v-model:value="vModel"
v-model:open="isOpen"
:list="basesList"
option-label-key="title"
search-input-placeholder="Search"
option-value-key="id"
close-on-select
:item-height="56"
class="!w-full"
container-class-name="!max-h-[171px]"
@update:value="copyValue"
>
<template #listItemContent="{ option }">
<div class="flex-1 flex flex-col truncate">
<div class="flex items-center gap-2">
<GeneralBaseIconColorPicker
:type="option?.type"
:model-value="parseProp(option.meta).iconColor"
size="xsmall"
readonly
>
</GeneralBaseIconColorPicker>
<div class="truncate text-nc-content-gray">
{{ option.id }}
</div>
</div>
<div class="w-full pl-7 text-nc-content-gray-muted text-small leading-[18px] truncate">
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ option?.title }}
</template>
{{ option?.title }}
</NcTooltip>
</div>
</div>
</template>
</NcList>
</template>
</NcDropdown>
<div
class="absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer transition-colors text-nc-content-gray-muted group-hover:text-nc-content-gray-subtle"
@click="copyValue"
>
<GeneralIcon v-if="copied" class="max-h-4 min-w-4 !text-current" icon="check" />
<GeneralIcon v-else class="max-h-4 min-w-4 !text-current" icon="copy" />
</div>
</div>
</template>

View File

@@ -0,0 +1,305 @@
<script setup lang="ts">
import type { IntegrationCategoryType, SyncDataType } from 'nocodb-sdk'
const props = defineProps<{
open: boolean
integrationType: IntegrationCategoryType
integrationSubType: SyncDataType
}>()
const emits = defineEmits(['update:open'])
const {
connectionDetails,
connectionUrl,
connectionHost,
selectedBase,
createConnectionDetails,
getConnectionDetails,
deleteConnectionDetails,
dataReflectionEnabled,
} = useDataReflection()
onMounted(async () => {
await getConnectionDetails()
})
</script>
<template>
<WorkspaceIntegrationsFormsEditOrAddCommonWrapper v-bind="props" @update:open="emits('update:open', $event)">
<template v-if="dataReflectionEnabled" #headerRightExtra>
<NcButton type="danger" size="small" @click="deleteConnectionDetails">Disable connection</NcButton>
</template>
<template #leftPanel="{ class: leftPanelClass }">
<div :class="leftPanelClass">
<div
v-if="!dataReflectionEnabled"
class="nc-nocodb-connection-details-placeholder flex flex-col gap-8 w-full h-full items-center justify-center text-center mt-10"
>
<img
src="~assets/img/placeholder/nocodb-pg-integration.png"
class="!w-full !max-w-[864px] flex-none"
alt="NocoDb X Pg integration"
/>
<span class="text-base font-bold">Connect with your favorite tools</span>
<span class="text-sm text-nc-content-gray-subtle2">Integrate with your favourite tools by bypassing our APIs</span>
<NcButton size="small" type="primary" @click="createConnectionDetails"> Get connection details </NcButton>
<div>
<!-- For spacing -->
</div>
</div>
<div v-else class="connection-details bg-white relative h-full flex flex-col w-full">
<div class="h-full max-h-[calc(100%_-_65px)] flex">
<div class="connection-details-left-panel nc-scrollbar-thin relative">
<div v-if="connectionDetails" class="h-full w-[768px] mx-auto">
<a-form
ref="form"
:model="connectionDetails"
hide-required-mark
name="external-base-create-form"
layout="vertical"
no-style
class="flex flex-col gap-5.5"
>
<div class="nc-form-section">
<div class="nc-form-section-title">General</div>
<div class="nc-form-section-body">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="Connection name">
<a-input value="NocoDB" disabled />
</a-form-item>
</a-col>
</a-row>
</div>
</div>
<div class="nc-form-section">
<div class="flex items-center justify-between">
<div class="nc-form-section-title">Connection details</div>
</div>
<div class="nc-form-section-body">
<a-row :gutter="24">
<a-col :span="24">
<a-form-item label="Connection URL">
<LazyWorkspaceIntegrationsConnectCopyInput v-model="connectionUrl" class="nc-connection-url" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="Host">
<LazyWorkspaceIntegrationsConnectCopyInput v-model="connectionHost" class="nc-connection-host" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="Port">
<LazyWorkspaceIntegrationsConnectCopyInput
v-model="connectionDetails.port"
class="nc-connection-port"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="Username">
<LazyWorkspaceIntegrationsConnectCopyInput
v-model="connectionDetails.username"
class="nc-connection-username"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="Password">
<LazyWorkspaceIntegrationsConnectCopyInput
v-model="connectionDetails.password"
password
class="nc-connection-password"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="Database">
<LazyWorkspaceIntegrationsConnectCopyInput
v-model="connectionDetails.database"
class="nc-connection-database"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="Schema">
<LazyWorkspaceIntegrationsConnectSchemaInput v-model="selectedBase" class="nc-connection-schema" />
</a-form-item>
</a-col>
</a-row>
</div>
</div>
<div>
<!-- For spacing -->
</div>
</a-form>
</div>
<general-overlay v-else :model-value="true" inline transition class="!bg-opacity-15">
<div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000">
<a-spin size="large" />
</div>
</general-overlay>
</div>
</div>
</div>
</div>
</template>
<template v-if="!dataReflectionEnabled" #rightPanel> </template>
</WorkspaceIntegrationsFormsEditOrAddCommonWrapper>
</template>
<style lang="scss" scoped>
.connection-details-left-panel {
@apply flex-1 flex justify-center;
}
:deep(.ant-collapse-header) {
@apply !-mt-4 !p-0 flex items-center !cursor-default children:first:flex;
}
:deep(.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow) {
@apply !right-0;
}
:deep(.ant-collapse-content-box) {
@apply !px-0 !pb-0 !pt-3;
}
:deep(.ant-form-item-explain-error) {
@apply !text-xs;
}
:deep(.ant-form-item) {
@apply mb-0;
}
:deep(.ant-divider) {
@apply m-0;
}
:deep(.ant-form-item-with-help .ant-form-item-explain) {
@apply !min-h-0;
}
:deep(.ant-select .ant-select-selector .ant-select-selection-item) {
@apply font-weight-400;
}
.connection-details {
:deep(.ant-input-affix-wrapper),
:deep(.ant-input),
:deep(.ant-select) {
@apply !appearance-none border-solid rounded-md;
&:disabled {
@apply bg-nc-bg-gray-extralight text-nc-content-gray-muted border-nc-border-gray-medium;
}
}
:deep(.ant-input-password) {
input {
@apply !border-none my-0;
}
}
.nc-form-section {
@apply flex flex-col gap-3;
}
.nc-form-section-title {
@apply text-sm font-bold text-gray-800;
}
.nc-form-section-body {
@apply flex flex-col gap-3;
}
.nc-connection-json-editor {
@apply min-h-[300px] max-h-[600px];
resize: vertical;
overflow-y: auto;
}
:deep(.ant-form-item-label > label.ant-form-item-required:after) {
@apply content-['*'] inline-block text-inherit text-red-500 ml-1;
}
:deep(.ant-form-item) {
&.ant-form-item-has-error {
&:not(:has(.ant-input-password)) .ant-input {
&:not(:hover):not(:focus):not(:disabled) {
@apply shadow-default;
}
&:hover:not(:focus):not(:disabled) {
@apply shadow-hover;
}
&:focus {
@apply shadow-error ring-0;
}
}
.ant-input-number,
.ant-input-affix-wrapper.ant-input-password {
&:not(:hover):not(:focus-within):not(:disabled) {
@apply shadow-default;
}
&:hover:not(:focus-within):not(:disabled) {
@apply shadow-hover;
}
&:focus-within {
@apply shadow-error ring-0;
}
}
}
&:not(.ant-form-item-has-error) {
&:not(:has(.ant-input-password)) .ant-input {
&:not(:hover):not(:focus):not(:disabled) {
@apply shadow-default border-gray-200;
}
&:hover:not(:focus):not(:disabled) {
@apply border-gray-200 shadow-hover;
}
&:focus {
@apply shadow-selected ring-0;
}
}
.ant-input-number,
.ant-input-affix-wrapper.ant-input-password {
&:not(:hover):not(:focus-within):not(:disabled) {
@apply shadow-default border-gray-200;
}
&:hover:not(:focus-within):not(:disabled) {
@apply border-gray-200 shadow-hover;
}
&:focus-within {
@apply shadow-selected ring-0;
}
}
}
}
:deep(.ant-row:not(.ant-form-item)) {
@apply !-mx-1.5;
& > .ant-col {
@apply !px-1.5;
}
}
}
</style>
<style lang="scss">
.nc-edit-or-add-integration-left-panel {
&:has(.nc-nocodb-connection-details-placeholder) {
@apply bg-nc-bg-gray-extralight;
}
}
</style>

View File

@@ -0,0 +1,102 @@
<script lang="ts" setup>
import { type IntegrationCategoryType, SyncDataType, clientTypes as _clientTypes } from '#imports'
const props = defineProps<{
open: boolean
integrationType: IntegrationCategoryType
integrationSubType: SyncDataType
}>()
const emit = defineEmits(['update:open'])
const vOpen = useVModel(props, 'open', emit)
const { isFromIntegrationPage, pageMode, IntegrationsPageMode, activeIntegration, activeIntegrationItem } = useIntegrationStore()
const isEditMode = computed(() => pageMode.value === IntegrationsPageMode.EDIT)
</script>
<template>
<div v-if="activeIntegration" class="h-full">
<div class="p-4 w-full flex items-center justify-between gap-3 border-b-1 border-gray-200">
<div class="flex-1 flex items-center gap-3">
<NcButton
v-if="!isEditMode && !isFromIntegrationPage"
type="text"
size="small"
@click="pageMode = IntegrationsPageMode.LIST"
>
<GeneralIcon icon="arrowLeft" />
</NcButton>
<div
v-if="activeIntegrationItem?.sub_type"
class="bg-nc-bg-gray-light rounded-md h-8 w-8 flex-none flex items-center justify-center children:flex-none"
>
<GeneralIntegrationIcon
:type="activeIntegrationItem.sub_type"
:size="activeIntegrationItem.sub_type === SyncDataType.NOCODB ? 'lg' : 'sm'"
/>
</div>
<div class="flex-1 text-base font-weight-700">{{ activeIntegration?.title }}</div>
</div>
<div class="flex items-center gap-3">
<slot name="headerRightExtra"> </slot>
<slot name="headerRight"> </slot>
<NcButton size="small" type="text" @click="vOpen = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
</div>
<div class="h-[calc(100%_-_66px)] flex">
<div class="nc-edit-or-add-integration-left-panel nc-scrollbar-thin relative">
<div class="w-full gap-4 max-w-[784px]">
<slot name="leftPanel" class="nc-edit-or-add-integration relative flex flex-col justify-center gap-2 w-full"> </slot>
</div>
</div>
<slot name="rightPanel" class="nc-edit-or-add-integration-right-panel">
<div v-if="!$slots.rightPanel" class="nc-edit-or-add-integration-right-panel">
<WorkspaceIntegrationsSupportedDocs />
<NcDivider />
</div>
</slot>
</div>
</div>
</template>
<style lang="scss" scoped>
.nc-form-item {
padding-right: 24px;
margin-bottom: 12px;
}
:deep(.ant-collapse-header) {
@apply !-mt-4 !p-0 flex items-center !cursor-default children:first:flex;
}
:deep(.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow) {
@apply !right-0;
}
:deep(.ant-collapse-content-box) {
@apply !px-0 !pb-0 !pt-3;
}
:deep(.ant-form-item-explain-error) {
@apply !text-xs;
}
:deep(.ant-divider) {
@apply m-0;
}
:deep(.ant-form-item-with-help .ant-form-item-explain) {
@apply !min-h-0;
}
:deep(.ant-select .ant-select-selector .ant-select-selection-item) {
@apply font-weight-400;
}
</style>
<style lang="scss"></style>

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import type { IntegrationCategoryType, SyncDataType } from '#imports'
import { clientTypes as _clientTypes } from '#imports'
import { type IntegrationCategoryType, SyncDataType, clientTypes as _clientTypes } from '#imports'
const props = defineProps<{
open: boolean
@@ -12,15 +11,8 @@ const emit = defineEmits(['update:open'])
const vOpen = useVModel(props, 'open', emit)
const {
isFromIntegrationPage,
pageMode,
IntegrationsPageMode,
activeIntegration,
activeIntegrationItem,
saveIntegration,
updateIntegration,
} = useIntegrationStore()
const { pageMode, IntegrationsPageMode, activeIntegration, activeIntegrationItem, saveIntegration, updateIntegration } =
useIntegrationStore()
const isEditMode = computed(() => pageMode.value === IntegrationsPageMode.EDIT)
@@ -87,52 +79,32 @@ onMounted(async () => {
</script>
<template>
<div v-if="activeIntegration" class="h-full">
<div class="p-4 w-full flex items-center justify-between gap-3 border-b-1 border-gray-200">
<div class="flex-1 flex items-center gap-3">
<NcButton
v-if="!isEditMode && !isFromIntegrationPage"
type="text"
size="small"
@click="pageMode = IntegrationsPageMode.LIST"
>
<GeneralIcon icon="arrowLeft" />
</NcButton>
<WorkspaceIntegrationsIcon :integration-item="activeIntegrationItem" size="xs" />
<div class="flex-1 text-base font-weight-700">{{ activeIntegration?.title }}</div>
<WorkspaceIntegrationsFormsEditOrAddCommonWrapper
v-if="activeIntegration && activeIntegrationItem?.dynamic"
v-bind="props"
@update:open="vOpen = $event"
>
<template #headerRight>
<NcButton
size="small"
type="primary"
:disabled="isLoading"
:loading="isLoading"
class="nc-extdb-btn-submit"
@click="submit"
>
{{ pageMode === IntegrationsPageMode.ADD ? 'Create integration' : 'Update integration' }}
</NcButton>
</template>
<template #leftPanel="{ class: leftPanelClass }">
<div :class="leftPanelClass">
<NcFormBuilder class="px-2" />
<div class="mt-10"></div>
</div>
<div class="flex items-center gap-3">
<NcButton
size="small"
type="primary"
:disabled="isLoading"
:loading="isLoading"
class="nc-extdb-btn-submit"
@click="submit"
>
{{ pageMode === IntegrationsPageMode.ADD ? 'Create integration' : 'Update integration' }}
</NcButton>
<NcButton size="small" type="text" @click="vOpen = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
</div>
<div class="h-[calc(100%_-_66px)] flex">
<div class="nc-edit-or-add-integration-left-panel nc-scrollbar-thin relative !px-4">
<div class="w-full gap-4 max-w-[784px]">
<div class="nc-edit-or-add-integration bg-white relative flex flex-col justify-center gap-2 w-full">
<NcFormBuilder class="px-2" />
<div class="mt-10"></div>
</div>
</div>
</div>
<div class="nc-edit-or-add-integration-right-panel">
<WorkspaceIntegrationsSupportedDocs />
<NcDivider />
</div>
</div>
</div>
</template>
</WorkspaceIntegrationsFormsEditOrAddCommonWrapper>
<WorkspaceIntegrationsConnect v-if="activeIntegration" v-bind="props" @update:open="vOpen = $event" />
<div v-else></div>
</template>
<style lang="scss" scoped>

View File

@@ -0,0 +1,13 @@
<script lang="ts" setup>
interface Props {
// Define your props here
}
const props = withDefaults(defineProps<Props>(), {
// Set default prop values here
})
</script>
<template></template>
<style lang="scss" scoped></style>