多维表格
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.

388 lines
11 KiB

<script lang="ts" setup>
import {
type BoolType,
feat: button field (#9144) * feat: static button type * fix: swagger * fix: style corrections * feat: webhook trigger * fix: disables * feat: Button icons fix: row Delete failing * fix: expanded-form ux fix: disable buttons in forms fix: update PlainCell button handling fix: webhook delete, active change handling * fix: disable case * fix: disable case * fix: update Button styles * fix: refactor min/max with limit for columns fix: disable filter, groupby for Button Field fix: disable aggregation for Buttons * fix: hide button field in Filters * fix: fields menu corrections fix: update menu corrections * fix: rebase * feat: support webhook creation from ButtonOptions * fix: sort related tests * fix: keep webhook modal open * fix: ui fixes * fix: icon duplicate * fix: syntax highlighing for handlebar fix: disable mascot fix: truncate selected webhook * fix: sort disable tooltip * test: button playwright test * fix: button column duplication * fix: add error fix: column options * fix: EditOrAddProvider.vue * fix: add invalid configuration * fix: ux corrections * fix: ux corrections * fix: error handling fix: clear single query cache on hook delete * fix: include button type in api * fix: update overlay styles fix: webhook update * fix: formula placeholder * fix: playwright tests * fix: playwright tests * feat: refactor formula input * fix: added more spacing * fix: no icon text * fix: lint * fix: handle invalid url * fix: handle sort by for button causes issue when button used as lookup * fix: button field position * docs: button field * fix: tooltip correction * fix: link * fix: add btn href * fix: handle some edge cases * fix: handle some edge cases * fix: update font color * fix: add manual trigger docs * fix: sqlite BaseModel fix * docs: button share view info * fix: rebase * fix: reduce height and added resize support fix: added tooltip if label overflow * fix: manual hook disable state * docs: manual trigger details in webhook page * fix: chevron grey shade to 500 * docs: sample payload for manual trigger * fix: style update * fix: pr review comments * fix: pr review changes * fix: reactivity issue * fix: reactivity issue * fix: filter enabled on button * fix: reload meta on webhook change * fix: error handling in formula filter * fix: handle url error --------- Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
4 months ago
type ButtonType,
type ColumnType,
type LookupType,
type RollupType,
dateFormats,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
timeFormats,
} from 'nocodb-sdk'
import dayjs from 'dayjs'
interface Props {
column: ColumnType
modelValue: any
bold?: BoolType
italic?: BoolType
underline?: BoolType
}
const props = defineProps<Props>()
const meta = inject(MetaInj)
const { t } = useI18n()
const { metas } = useMetas()
const column = toRef(props, 'column')
const { sqlUis } = storeToRefs(useBase())
const basesStore = useBases()
const { basesUser } = storeToRefs(basesStore)
const { isXcdbBase, isMssql, isMysql } = useBase()
5 months ago
const sqlUi = ref(
column.value?.source_id && sqlUis.value[column.value?.source_id]
? sqlUis.value[column.value?.source_id]
: Object.values(sqlUis.value)[0],
)
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
const getCheckBoxValue = (modelValue: boolean | string | number | '0' | '1') => {
return !!modelValue && modelValue !== '0' && modelValue !== 0 && modelValue !== 'false'
}
const getMultiSelectValue = (modelValue: any, col: ColumnType): string => {
if (!modelValue) {
return ''
}
return modelValue
? Array.isArray(modelValue)
? modelValue.join(', ')
: modelValue.toString()
: isMysql(col.source_id)
? modelValue.toString().split(',').join(', ')
: modelValue.split(', ')
}
const getDateValue = (modelValue: string | null | number, col: ColumnType, isSystemCol?: boolean) => {
const dateFormat = !isSystemCol ? parseProp(col.meta)?.date_format ?? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'
if (!modelValue || !dayjs(modelValue).isValid()) {
return ''
} else {
return dayjs(/^\d+$/.test(String(modelValue)) ? +modelValue : modelValue).format(dateFormat)
}
}
const getYearValue = (modelValue: string | null) => {
if (!modelValue) {
return ''
} else if (!dayjs(modelValue).isValid()) {
return ''
} else {
return dayjs(modelValue.toString(), 'YYYY').format('YYYY')
}
}
const getDateTimeValue = (modelValue: string | null, col: ColumnType) => {
if (!modelValue || !dayjs(modelValue).isValid()) {
return ''
}
const dateFormat = parseProp(col?.meta)?.date_format ?? dateFormats[0]
const timeFormat = parseProp(col?.meta)?.time_format ?? timeFormats[0]
const dateTimeFormat = `${dateFormat} ${timeFormat}`
const isXcDB = isXcdbBase(col.source_id)
if (!isXcDB) {
return dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, dateTimeFormat).format(dateTimeFormat)
}
if (isMssql(col.source_id)) {
// e.g. 2023-04-29T11:41:53.000Z
return dayjs(modelValue, dateTimeFormat).format(dateTimeFormat)
} else {
return dayjs(modelValue).utc().local().format(dateTimeFormat)
}
}
const getTimeValue = (modelValue: string | null) => {
if (!modelValue) {
return ''
}
let dateTime = dayjs(modelValue)
if (!dateTime.isValid()) {
dateTime = dayjs(modelValue, 'HH:mm:ss')
}
if (!dateTime.isValid()) {
dateTime = dayjs(`1999-01-01 ${modelValue}`)
}
if (!dateTime.isValid()) {
return ''
}
return dateTime.format('HH:mm')
}
const getDurationValue = (modelValue: string | null, col: ColumnType) => {
const durationType = parseProp(col.meta)?.duration || 0
return convertMS2Duration(modelValue, durationType)
}
const getPercentValue = (modelValue: string | null) => {
return modelValue ? `${modelValue}%` : ''
}
const getCurrencyValue = (modelValue: string | number | null | undefined, col: ColumnType): string => {
const currencyMeta = {
currency_locale: 'en-US',
currency_code: 'USD',
...parseProp(col.meta),
}
try {
if (modelValue === null || modelValue === undefined || isNaN(modelValue)) {
return modelValue === null || modelValue === undefined ? '' : (modelValue as string)
}
return new Intl.NumberFormat(currencyMeta.currency_locale || 'en-US', {
style: 'currency',
currency: currencyMeta.currency_code || 'USD',
}).format(+modelValue)
} catch (e) {
return modelValue as string
}
}
const getUserValue = (modelValue: string | string[] | null | Array<any>) => {
if (!modelValue) {
return ''
}
const baseUsers = meta?.value.base_id ? basesUser.value.get(meta?.value.base_id) || [] : []
if (typeof modelValue === 'string') {
const idsOrMails = modelValue.split(',')
return idsOrMails
.map((idOrMail) => {
const user = baseUsers.find((u) => u.id === idOrMail || u.email === idOrMail)
return user ? user.display_name || user.email : idOrMail.id
})
.join(', ')
} else {
if (Array.isArray(modelValue)) {
return modelValue
.map((idOrMail) => {
const user = baseUsers.find((u) => u.id === idOrMail.id || u.email === idOrMail.email)
return user ? user.display_name || user.email : idOrMail.id
})
.join(', ')
} else {
return modelValue ? modelValue.display_name || modelValue.email : ''
}
}
}
const getDecimalValue = (modelValue: string | null | number, col: ColumnType) => {
if (!modelValue || isNaN(Number(modelValue))) {
return ''
}
const columnMeta = parseProp(col.meta)
return Number(modelValue).toFixed(columnMeta?.precision ?? 1)
}
const getIntValue = (modelValue: string | null | number) => {
if (!modelValue || isNaN(Number(modelValue))) {
return ''
}
return Number(modelValue) as unknown as string
}
const getTextAreaValue = (modelValue: string | null, col: ColumnType) => {
const isRichMode = typeof col.meta === 'string' ? JSON.parse(col.meta).richMode : col.meta?.richMode
if (isRichMode) {
return modelValue?.replace(/[*_~\[\]]|<\/?[^>]+(>|$)/g, '') || ''
}
return modelValue || ''
}
const getRollupValue = (modelValue: string | null | number, col: ColumnType) => {
const colOptions = col.colOptions as RollupType
const fns = ['count', 'avg', 'sum', 'countDistinct', 'sumDistinct', 'avgDistinct']
if (fns.includes(colOptions.rollup_function!)) {
return modelValue as string
} else {
const relationColumnOptions = colOptions.fk_relation_column_id
? meta?.value.columns?.find((c) => c.id === colOptions.fk_relation_column_id)?.colOptions
: null
const relatedTableMeta =
relationColumnOptions?.fk_related_model_id && metas.value?.[relationColumnOptions.fk_related_model_id as string]
const childColumn = relatedTableMeta?.columns.find((c: ColumnType) => c.id === colOptions.fk_rollup_column_id)
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return parseValue(modelValue, childColumn) as string
}
}
const getLookupValue = (modelValue: string | null | number | Array<any>, col: ColumnType) => {
const colOptions = col.colOptions as LookupType
const relationColumnOptions = colOptions.fk_relation_column_id
? meta?.value.columns?.find((c) => c.id === colOptions.fk_relation_column_id)?.colOptions
: null
const relatedTableMeta =
relationColumnOptions?.fk_related_model_id && metas.value?.[relationColumnOptions.fk_related_model_id as string]
const childColumn = relatedTableMeta?.columns.find((c: ColumnType) => c.id === colOptions.fk_lookup_column_id) as
| ColumnType
| undefined
if (Array.isArray(modelValue)) {
return modelValue
.map((v) => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return parseValue(v, childColumn!)
})
.join(', ')
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return parseValue(modelValue, childColumn!)
}
const getAttachmentValue = (modelValue: string | null | number | Array<any>) => {
if (Array.isArray(modelValue)) {
return modelValue.map((v) => `${v.title}`).join(', ')
}
return modelValue as string
}
const getLinksValue = (modelValue: string, col: ColumnType) => {
if (typeof col.meta === 'string') {
col.meta = JSON.parse(col.meta)
}
const parsedValue = +modelValue || 0
if (!parsedValue) {
return ''
} else if (parsedValue === 1) {
return `1 ${col?.meta?.singular || t('general.link')}`
} else {
return `${parsedValue} ${col?.meta?.plural || t('general.links')}`
}
}
const parseValue = (value: any, col: ColumnType): string => {
if (!col) {
return ''
}
if (isGeoData(col)) {
const [latitude, longitude] = ((value as string) || '').split(';')
return latitude && longitude ? `${latitude}; ${longitude}` : value
}
if (isTextArea(col)) {
return getTextAreaValue(value, col)
}
if (isBoolean(col, abstractType)) {
return getCheckBoxValue(value) ? 'Checked' : 'Unchecked'
}
if (isMultiSelect(col)) {
return getMultiSelectValue(value, col)
}
if (isDate(col, abstractType)) {
return getDateValue(value, col)
}
if (isYear(col, abstractType)) {
return getYearValue(value)
}
if (isDateTime(col, abstractType)) {
return getDateTimeValue(value, col)
}
if (isTime(col, abstractType)) {
return getTimeValue(value)
}
if (isDuration(col)) {
return getDurationValue(value, col)
}
if (isPercent(col)) {
return getPercentValue(value)
}
if (isCurrency(col)) {
return getCurrencyValue(value, col)
}
if (isUser(col)) {
return getUserValue(value)
}
if (isDecimal(col)) {
return getDecimalValue(value, col)
}
if (isInt(col, abstractType)) {
return getIntValue(value)
}
if (isJSON(col)) {
return JSON.stringify(value, null, 2)
}
if (isRollup(col)) {
return getRollupValue(value, col)
}
if (isLookup(col) || isLTAR(col.uidt, col.colOptions)) {
return getLookupValue(value, col)
}
if (isCreatedOrLastModifiedTimeCol(col)) {
return getDateValue(value, col, true)
}
if (isCreatedOrLastModifiedByCol(col)) {
return getUserValue(value)
}
if (isAttachment(col)) {
return getAttachmentValue(value)
}
if (isLink(col)) {
return getLinksValue(value, col)
}
if (isFormula(col) && col?.meta?.display_type) {
return parseValue(value, {
uidt: col?.meta?.display_type,
...col?.meta?.display_column_meta,
})
}
feat: button field (#9144) * feat: static button type * fix: swagger * fix: style corrections * feat: webhook trigger * fix: disables * feat: Button icons fix: row Delete failing * fix: expanded-form ux fix: disable buttons in forms fix: update PlainCell button handling fix: webhook delete, active change handling * fix: disable case * fix: disable case * fix: update Button styles * fix: refactor min/max with limit for columns fix: disable filter, groupby for Button Field fix: disable aggregation for Buttons * fix: hide button field in Filters * fix: fields menu corrections fix: update menu corrections * fix: rebase * feat: support webhook creation from ButtonOptions * fix: sort related tests * fix: keep webhook modal open * fix: ui fixes * fix: icon duplicate * fix: syntax highlighing for handlebar fix: disable mascot fix: truncate selected webhook * fix: sort disable tooltip * test: button playwright test * fix: button column duplication * fix: add error fix: column options * fix: EditOrAddProvider.vue * fix: add invalid configuration * fix: ux corrections * fix: ux corrections * fix: error handling fix: clear single query cache on hook delete * fix: include button type in api * fix: update overlay styles fix: webhook update * fix: formula placeholder * fix: playwright tests * fix: playwright tests * feat: refactor formula input * fix: added more spacing * fix: no icon text * fix: lint * fix: handle invalid url * fix: handle sort by for button causes issue when button used as lookup * fix: button field position * docs: button field * fix: tooltip correction * fix: link * fix: add btn href * fix: handle some edge cases * fix: handle some edge cases * fix: update font color * fix: add manual trigger docs * fix: sqlite BaseModel fix * docs: button share view info * fix: rebase * fix: reduce height and added resize support fix: added tooltip if label overflow * fix: manual hook disable state * docs: manual trigger details in webhook page * fix: chevron grey shade to 500 * docs: sample payload for manual trigger * fix: style update * fix: pr review comments * fix: pr review changes * fix: reactivity issue * fix: reactivity issue * fix: filter enabled on button * fix: reload meta on webhook change * fix: error handling in formula filter * fix: handle url error --------- Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
4 months ago
if (isButton(col)) {
if ((col.colOptions as ButtonType).type === 'url') return value
else return col.colOptions?.label
}
return value as unknown as string
}
</script>
<template>
<span
class="plain-cell before:px-1"
:class="{
'!font-bold': bold,
'italic': italic,
'underline': underline,
}"
data-testid="nc-plain-cell"
>
{{ parseValue(modelValue, column) }}
</span>
</template>
<style lang="scss" scoped>
.plain-cell {
&::before {
content: '•';
padding: 0 4px;
}
&:first-child::before {
content: '';
padding: 0;
}
&:first-child {
padding-left: 0;
}
}
</style>