mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
10 KiB
455 lines
10 KiB
<script setup lang="ts"> |
|
import { ButtonActionsType, type ButtonType, type ColumnType } from 'nocodb-sdk' |
|
import type { Ref } from 'vue' |
|
|
|
const column = inject(ColumnInj) as Ref< |
|
ColumnType & { |
|
colOptions: ButtonType |
|
} |
|
> |
|
|
|
const cellValue = inject(CellValueInj, ref()) |
|
|
|
const { currentRow } = useSmartsheetRowStoreOrThrow() |
|
|
|
const { generateRows, generatingRows, generatingColumnRows, generatingColumns, aiIntegrations } = useNocoAi() |
|
|
|
const isFieldAiIntegrationAvailable = computed(() => { |
|
if (column.value?.colOptions?.type !== ButtonActionsType.Ai) return true |
|
|
|
const fkIntegrationId = column.value?.colOptions?.fk_integration_id |
|
|
|
if (!fkIntegrationId) return false |
|
|
|
return ncIsArrayIncludes(aiIntegrations.value, fkIntegrationId, 'id') |
|
}) |
|
|
|
const meta = inject(MetaInj, ref()) |
|
|
|
const isGrid = inject(IsGridInj, ref(false)) |
|
|
|
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false)) |
|
|
|
const { isUIAllowed } = useRoles() |
|
|
|
const isPublic = inject(IsPublicInj, ref(false)) |
|
|
|
const { $api } = useNuxtApp() |
|
|
|
const rowId = computed(() => { |
|
return extractPkFromRow(currentRow.value?.row, meta.value!.columns!) |
|
}) |
|
|
|
const isLoading = ref(false) |
|
|
|
const pk = computed(() => { |
|
if (!meta.value?.columns) return |
|
return extractPkFromRow(currentRow.value?.row, meta.value.columns) |
|
}) |
|
|
|
const generate = async () => { |
|
if (!meta?.value?.id || !meta.value.columns || !column?.value?.id) return |
|
|
|
if (!pk.value) return |
|
|
|
const outputColumnIds = |
|
ncIsString(column.value.colOptions?.output_column_ids) && column.value.colOptions.output_column_ids.split(',').length > 0 |
|
? column.value.colOptions.output_column_ids.split(',') |
|
: [] |
|
const outputColumns = outputColumnIds.map((id) => meta.value?.columnsById[id]) |
|
|
|
generatingRows.value.push(pk.value) |
|
generatingColumnRows.value.push(column.value.id) |
|
|
|
generatingColumns.value.push(...(outputColumnIds ?? [])) |
|
|
|
const res = await generateRows(meta.value.id, column.value.id, [pk.value]) |
|
|
|
if (res?.length) { |
|
const resRow = res[0] |
|
|
|
if (outputColumnIds) { |
|
for (const col of outputColumns) { |
|
if (col && currentRow.value.row) { |
|
currentRow.value.row[col.title!] = resRow[col.title!] |
|
} |
|
} |
|
} |
|
} |
|
|
|
generatingRows.value = generatingRows.value.filter((v) => v !== pk.value) |
|
generatingColumnRows.value = generatingColumnRows.value.filter((v) => v !== column.value.id) |
|
generatingColumns.value = generatingColumns.value.filter((v) => !outputColumnIds?.includes(v)) |
|
} |
|
|
|
const triggerAction = async () => { |
|
const colOptions = column.value.colOptions |
|
|
|
if (colOptions.type === ButtonActionsType.Webhook) { |
|
try { |
|
isLoading.value = true |
|
|
|
await $api.dbTableWebhook.trigger(cellValue.value?.fk_webhook_id, rowId!.value) |
|
} catch (e) { |
|
console.log(e) |
|
message.error(await extractSdkResponseErrorMsg(e)) |
|
} finally { |
|
isLoading.value = false |
|
} |
|
} else if (colOptions.type === ButtonActionsType.Ai) { |
|
await generate() |
|
} |
|
} |
|
|
|
const componentProps = computed(() => { |
|
if (column.value.colOptions.type === ButtonActionsType.Url) { |
|
let url = `${cellValue.value?.url}` |
|
url = /^(https?|ftp|mailto|file):\/\//.test(url) ? url : url.trim() ? `https://${url}` : '' |
|
|
|
// if url params not encoded, encode them using encodeURI |
|
try { |
|
url = decodeURI(url) === url ? encodeURI(url) : url |
|
} catch { |
|
url = encodeURI(url) |
|
} |
|
|
|
return { |
|
href: url, |
|
target: '_blank', |
|
...(column.value?.colOptions.error ? { disabled: true } : {}), |
|
} |
|
} else if (column.value.colOptions.type === ButtonActionsType.Webhook) { |
|
return { |
|
disabled: |
|
isPublic.value || |
|
!isUIAllowed('hookTrigger') || |
|
isLoading.value || |
|
!column.value.colOptions.fk_webhook_id || |
|
!cellValue.value?.fk_webhook_id, |
|
} |
|
} else if (column.value.colOptions.type === ButtonActionsType.Ai) { |
|
return { |
|
disabled: |
|
!isFieldAiIntegrationAvailable.value || |
|
isLoading.value || |
|
(pk.value && |
|
generatingRows.value.includes(pk.value) && |
|
column.value?.id && |
|
generatingColumnRows.value.includes(column.value.id)), |
|
} |
|
} |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div |
|
:class="{ |
|
'justify-center': isGrid && !isExpandedForm, |
|
}" |
|
class="w-full flex items-center" |
|
> |
|
<NcTooltip :disabled="isFieldAiIntegrationAvailable" class="flex"> |
|
<template #title> |
|
{{ aiIntegrations.length ? $t('tooltip.aiIntegrationReConfigure') : $t('tooltip.aiIntegrationAddAndReConfigure') }} |
|
</template> |
|
<component |
|
:is="column.colOptions.type === ButtonActionsType.Url ? 'a' : 'button'" |
|
v-bind="componentProps" |
|
data-testid="nc-button-cell" |
|
:class="[ |
|
`${column.colOptions.color ?? 'brand'} ${column.colOptions.theme ?? 'solid'}`, |
|
{ '!w-6': !column.colOptions.label }, |
|
]" |
|
class="nc-cell-button nc-button-cell-link btn-cell-colors truncate flex items-center h-6" |
|
@click="triggerAction" |
|
> |
|
<GeneralLoader |
|
v-if="isLoading || (pk && generatingRows.includes(pk) && column?.id && generatingColumnRows.includes(column.id))" |
|
:class="{ |
|
solid: column.colOptions.theme === 'solid', |
|
text: column.colOptions.theme === 'text', |
|
light: column.colOptions.theme === 'light', |
|
}" |
|
class="flex btn-cell-colors !bg-transparent w-4 h-4" |
|
size="medium" |
|
/> |
|
<GeneralIcon v-else-if="column.colOptions.icon" :icon="column.colOptions.icon" class="!w-4 min-w-4 min-h-4 !h-4" /> |
|
<NcTooltip v-if="column.colOptions.label" class="!truncate" show-on-truncate-only> |
|
<span class="text-[13px] truncate font-medium"> |
|
{{ column.colOptions.label }} |
|
</span> |
|
<template #title> |
|
{{ column.colOptions.label }} |
|
</template> |
|
</NcTooltip> |
|
</component> |
|
</NcTooltip> |
|
</div> |
|
</template> |
|
|
|
<style lang="scss"> |
|
.nc-data-cell { |
|
&:has(.nc-virtual-cell-button) { |
|
@apply !border-none; |
|
box-shadow: none !important; |
|
|
|
&:focus-within:not(.nc-readonly-div-data-cell):not(.nc-system-field) { |
|
box-shadow: none !important; |
|
} |
|
} |
|
|
|
.nc-cell-attachment { |
|
@apply !border-none; |
|
} |
|
} |
|
|
|
.nc-button-cell-link { |
|
@apply !no-underline; |
|
} |
|
</style> |
|
|
|
<style scoped lang="scss"> |
|
.nc-cell-button { |
|
@apply rounded-lg px-2 flex items-center gap-2 transition-all justify-center; |
|
&:not([class*='text']) { |
|
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.06), 0px 5px 3px -2px rgba(0, 0, 0, 0.02); |
|
} |
|
&:focus-within { |
|
@apply outline-none ring-0; |
|
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe; |
|
} |
|
&[disabled] { |
|
@apply !bg-gray-100 text-gray-400; |
|
} |
|
} |
|
|
|
.btn-cell-colors { |
|
&.solid { |
|
@apply text-white; |
|
|
|
&.brand { |
|
@apply bg-brand-500 hover:bg-brand-600; |
|
.nc-loader { |
|
@apply !text-brand-500; |
|
} |
|
} |
|
|
|
&.red { |
|
@apply bg-red-600 hover:bg-red-700; |
|
.nc-loader { |
|
@apply !text-red-600; |
|
} |
|
} |
|
|
|
&.green { |
|
@apply bg-green-600 hover:bg-green-700; |
|
.nc-loader { |
|
@apply !text-green-600; |
|
} |
|
} |
|
|
|
&.maroon { |
|
@apply bg-maroon-600 hover:bg-maroon-700; |
|
.nc-loader { |
|
@apply !text-maroon-600; |
|
} |
|
} |
|
|
|
&.blue { |
|
@apply bg-blue-600 hover:bg-blue-700; |
|
.nc-loader { |
|
@apply !text-blue-600; |
|
} |
|
} |
|
|
|
&.orange { |
|
@apply bg-orange-600 hover:bg-orange-700; |
|
.nc-loader { |
|
@apply !text-orange-600; |
|
} |
|
} |
|
|
|
&.pink { |
|
@apply bg-pink-600 hover:bg-pink-700; |
|
.nc-loader { |
|
@apply !text-pink-600; |
|
} |
|
} |
|
|
|
&.purple { |
|
@apply bg-purple-500 hover:bg-purple-700; |
|
.nc-loader { |
|
@apply !text-purple-600; |
|
} |
|
} |
|
|
|
&.yellow { |
|
@apply bg-yellow-600 hover:bg-yellow-700; |
|
.nc-loader { |
|
@apply !text-yellow-600; |
|
} |
|
} |
|
|
|
&.gray { |
|
@apply bg-gray-600 hover:bg-gray-700; |
|
.nc-loader { |
|
@apply !text-gray-600; |
|
} |
|
} |
|
} |
|
|
|
&.light { |
|
@apply text-gray-700; |
|
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.06), 0px 5px 3px -2px rgba(0, 0, 0, 0.02); |
|
|
|
&.brand { |
|
@apply bg-brand-100 hover:bg-brand-200; |
|
.nc-loader { |
|
@apply !text-brand-500; |
|
} |
|
} |
|
|
|
&.red { |
|
@apply bg-red-100 hover:bg-red-200; |
|
.nc-loader { |
|
@apply !text-red-600; |
|
} |
|
} |
|
|
|
&.green { |
|
@apply bg-green-100 hover:bg-green-200; |
|
.nc-loader { |
|
@apply !text-green-600; |
|
} |
|
} |
|
|
|
&.maroon { |
|
@apply bg-maroon-100 hover:bg-maroon-200; |
|
.nc-loader { |
|
@apply !text-maroon-600; |
|
} |
|
} |
|
|
|
&.blue { |
|
@apply bg-blue-100 hover:bg-blue-200; |
|
.nc-loader { |
|
@apply !text-blue-600; |
|
} |
|
} |
|
|
|
&.orange { |
|
@apply bg-orange-100 hover:bg-orange-200; |
|
.nc-loader { |
|
@apply !text-orange-600; |
|
} |
|
} |
|
|
|
&.pink { |
|
@apply bg-pink-100 hover:bg-pink-200; |
|
.nc-loader { |
|
@apply !text-pink-600; |
|
} |
|
} |
|
|
|
&.purple { |
|
@apply bg-purple-100 hover:bg-purple-200; |
|
.nc-loader { |
|
@apply !text-purple-600; |
|
} |
|
} |
|
|
|
&.yellow { |
|
@apply bg-yellow-100 hover:bg-yellow-200; |
|
.nc-loader { |
|
@apply !text-yellow-600; |
|
} |
|
} |
|
|
|
&.gray { |
|
@apply bg-gray-100 hover:bg-gray-200; |
|
.nc-loader { |
|
@apply !text-gray-600; |
|
} |
|
} |
|
} |
|
|
|
&.text { |
|
&:hover { |
|
@apply bg-gray-200; |
|
} |
|
&:focus { |
|
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe; |
|
} |
|
|
|
&.brand { |
|
@apply text-brand-500; |
|
.nc-loader { |
|
@apply !text-brand-500; |
|
} |
|
} |
|
|
|
&.red { |
|
@apply text-red-600; |
|
.nc-loader { |
|
@apply !text-red-600; |
|
} |
|
} |
|
|
|
&.green { |
|
@apply text-green-600; |
|
.nc-loader { |
|
@apply !text-green-600; |
|
} |
|
} |
|
|
|
&.maroon { |
|
@apply text-maroon-600; |
|
.nc-loader { |
|
@apply !text-maroon-600; |
|
} |
|
} |
|
|
|
&.blue { |
|
@apply text-blue-600; |
|
.nc-loader { |
|
@apply !text-blue-600; |
|
} |
|
} |
|
|
|
&.orange { |
|
@apply text-orange-600; |
|
.nc-loader { |
|
@apply !text-orange-600; |
|
} |
|
} |
|
|
|
&.pink { |
|
@apply text-pink-600; |
|
.nc-loader { |
|
@apply !text-pink-600; |
|
} |
|
} |
|
|
|
&.purple { |
|
@apply text-purple-500; |
|
.nc-loader { |
|
@apply !text-purple-600; |
|
} |
|
} |
|
|
|
&.yellow { |
|
@apply text-yellow-600; |
|
.nc-loader { |
|
@apply !text-yellow-600; |
|
} |
|
} |
|
|
|
&.gray { |
|
@apply text-gray-600; |
|
.nc-loader { |
|
@apply !text-gray-600; |
|
} |
|
} |
|
} |
|
} |
|
</style>
|
|
|