Browse Source

feat: Use lookup over attachment field as cover image in gallery & kanban view (#8801)

* feat(nc-gui): Use lookup over attachment field as cover image in gallery & kanban view

* chore(nc-gui): lint

* fix(nc-gui): nested attchment lookup cell issue

* fix(nc-gui): pr review changes
pull/8871/head
Ramesh Mane 5 months ago committed by GitHub
parent
commit
6e2cfdc20b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 59
      packages/nc-gui/components/dlg/ViewCreate.vue
  2. 13
      packages/nc-gui/components/smartsheet/Gallery.vue
  3. 13
      packages/nc-gui/components/smartsheet/Kanban.vue
  4. 89
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  5. 6
      packages/nc-gui/components/virtual-cell/Lookup.vue

59
packages/nc-gui/components/dlg/ViewCreate.vue

@ -2,7 +2,17 @@
import type { ComponentPublicInstance } from '@vue/runtime-core'
import { capitalize } from '@vue/runtime-core'
import type { Form as AntForm, SelectProps } from 'ant-design-vue'
import type { CalendarType, FormType, GalleryType, GridType, KanbanType, MapType, TableType } from 'nocodb-sdk'
import type {
CalendarType,
ColumnType,
FormType,
GalleryType,
GridType,
KanbanType,
LookupType,
MapType,
TableType,
} from 'nocodb-sdk'
import { UITypes, ViewTypes, isSystemColumn } from 'nocodb-sdk'
interface Props {
@ -52,7 +62,7 @@ const props = withDefaults(defineProps<Props>(), {
const emits = defineEmits<Emits>()
const { getMeta } = useMetas()
const { metas, getMeta } = useMetas()
const { viewsByTable } = storeToRefs(useViewsStore())
@ -266,8 +276,8 @@ onMounted(async () => {
if (props.type === ViewTypes.GALLERY) {
viewSelectFieldOptions.value = [
{ value: null, label: 'No Image' },
...meta.value
.columns!.filter((el) => el.uidt === UITypes.Attachment)
...(meta.value.columns || [])
.filter((el) => el.uidt === UITypes.Attachment)
.map((field) => {
return {
value: field.id,
@ -276,6 +286,47 @@ onMounted(async () => {
}
}),
]
const lookupColumns = (meta.value.columns || [])?.filter((c) => c.uidt === UITypes.Lookup)
const attLookupColumnIds: Set<string> = new Set()
const loadLookupMeta = async (originalCol: ColumnType, column: ColumnType, metaId?: string): Promise<void> => {
const relationColumn =
metaId || meta.value?.id
? metas.value[metaId || meta.value?.id]?.columns?.find(
(c: ColumnType) => c.id === (column?.colOptions as LookupType)?.fk_relation_column_id,
)
: undefined
if (relationColumn?.colOptions?.fk_related_model_id) {
await getMeta(relationColumn.colOptions.fk_related_model_id!)
const lookupColumn = metas.value[relationColumn.colOptions.fk_related_model_id]?.columns?.find(
(c: any) => c.id === (column?.colOptions as LookupType)?.fk_lookup_column_id,
) as ColumnType | undefined
if (lookupColumn && isAttachment(lookupColumn)) {
attLookupColumnIds.add(originalCol.id)
return
} else if (lookupColumn && lookupColumn?.uidt === UITypes.Lookup) {
await loadLookupMeta(originalCol, lookupColumn, relationColumn.colOptions.fk_related_model_id)
}
}
}
await Promise.allSettled(lookupColumns.map((col) => loadLookupMeta(col, col)))
const lookupAttColumns = lookupColumns
.filter((column) => attLookupColumnIds.has(column?.id))
.map((c) => {
return {
value: c.id,
label: c.title,
uidt: c.uidt,
}
})
viewSelectFieldOptions.value = [...viewSelectFieldOptions.value, ...lookupAttColumns]
if (coverImageColumnId.value) {
form.fk_cover_image_col_id = coverImageColumnId.value

13
packages/nc-gui/components/smartsheet/Gallery.vue

@ -87,12 +87,21 @@ const showContextMenu = (e: MouseEvent, target?: { row: RowType; index: number }
}
const attachments = (record: any): Attachment[] => {
if (!coverImageColumn.value?.title || !record.row[coverImageColumn.value.title]) return []
try {
if (coverImageColumn.value?.title && record.row[coverImageColumn.value.title]) {
return typeof record.row[coverImageColumn.value.title] === 'string'
const att =
typeof record.row[coverImageColumn.value.title] === 'string'
? JSON.parse(record.row[coverImageColumn.value.title])
: record.row[coverImageColumn.value.title]
if (Array.isArray(att)) {
return att
.flat()
.map((a) => (typeof a === 'string' ? JSON.parse(a) : a))
.filter((a) => a && !Array.isArray(a) && typeof a === 'object' && Object.keys(a).length)
}
return []
} catch (e) {
return []

13
packages/nc-gui/components/smartsheet/Kanban.vue

@ -118,12 +118,21 @@ reloadViewDataHook?.on(async () => {
})
const attachments = (record: any): Attachment[] => {
if (!coverImageColumn.value?.title || !record.row[coverImageColumn.value.title]) return []
try {
if (coverImageColumn.value?.title && record.row[coverImageColumn.value.title]) {
return typeof record.row[coverImageColumn.value.title] === 'string'
const att =
typeof record.row[coverImageColumn.value.title] === 'string'
? JSON.parse(record.row[coverImageColumn.value.title])
: record.row[coverImageColumn.value.title]
if (Array.isArray(att)) {
return att
.flat()
.map((a) => (typeof a === 'string' ? JSON.parse(a) : a))
.filter((a) => a && !Array.isArray(a) && typeof a === 'object' && Object.keys(a).length)
}
return []
} catch (e) {
return []

89
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { CalendarType, ColumnType, GalleryType, KanbanType } from 'nocodb-sdk'
import type { CalendarType, ColumnType, GalleryType, KanbanType, LookupType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import Draggable from 'vuedraggable'
@ -7,6 +7,8 @@ import type { SelectProps } from 'ant-design-vue'
const activeView = inject(ActiveViewInj, ref())
const meta = inject(MetaInj, ref())
const reloadViewMetaHook = inject(ReloadViewMetaHookInj, undefined)!
const reloadViewDataHook = inject(ReloadViewDataHookInj, undefined)!
@ -21,6 +23,8 @@ const { $api, $e } = useNuxtApp()
const { t } = useI18n()
const { metas, getMeta } = useMetas()
const {
showSystemFields,
fields,
@ -113,18 +117,7 @@ const onMove = async (_event: { moved: { newIndex: number; oldIndex: number } },
}
}
const coverOptions = computed<SelectProps['options']>(() => {
const filterFields =
fields.value
?.filter((el) => el.fk_column_id && metaColumnById.value[el.fk_column_id].uidt === UITypes.Attachment)
.map((field) => {
return {
value: field.fk_column_id,
label: field.title,
}
}) ?? []
return [{ value: null, label: 'No Image' }, ...filterFields]
})
const coverOptions = ref<SelectProps['options']>([])
const updateCoverImage = async (val?: string | null) => {
if (
@ -365,6 +358,72 @@ watch(open, (value) => {
}, 100)
})
watch(
fields,
async (newValue) => {
if (!newValue || isPublic.value || ![ViewTypes.GALLERY, ViewTypes.KANBAN].includes(activeView.value?.type as ViewTypes))
return
const filterFields =
newValue
.filter((el) => el.fk_column_id && metaColumnById.value[el.fk_column_id].uidt === UITypes.Attachment)
.map((field) => {
return {
value: field.fk_column_id,
label: field.title,
}
}) ?? []
coverOptions.value = [{ value: null, label: 'No Image' }, ...filterFields]
const lookupColumns = newValue
.filter((f) => f.fk_column_id && metaColumnById.value[f.fk_column_id].uidt === UITypes.Lookup)
.map((f) => metaColumnById.value[f.fk_column_id!])
const attLookupColumnIds: Set<string> = new Set()
const loadLookupMeta = async (originalCol: ColumnType, column: ColumnType, metaId?: string): Promise<void> => {
const relationColumn =
metaId || meta.value?.id
? metas.value[metaId || meta.value?.id]?.columns?.find(
(c: ColumnType) => c.id === (column?.colOptions as LookupType)?.fk_relation_column_id,
)
: undefined
if (relationColumn?.colOptions?.fk_related_model_id) {
await getMeta(relationColumn.colOptions.fk_related_model_id!)
const lookupColumn = metas.value[relationColumn.colOptions.fk_related_model_id]?.columns?.find(
(c: any) => c.id === (column?.colOptions as LookupType)?.fk_lookup_column_id,
) as ColumnType | undefined
if (lookupColumn && isAttachment(lookupColumn)) {
attLookupColumnIds.add(originalCol.id)
return
} else if (lookupColumn && lookupColumn?.uidt === UITypes.Lookup) {
await loadLookupMeta(originalCol, lookupColumn, relationColumn.colOptions.fk_related_model_id)
}
}
}
await Promise.allSettled(lookupColumns.map((col) => loadLookupMeta(col, col)))
const lookupAttColumns = lookupColumns
.filter((column) => attLookupColumnIds.has(column?.id))
.map((c) => {
return {
value: c.id,
label: c.title,
}
})
coverOptions.value = [...coverOptions.value, ...lookupAttColumns]
},
{
immediate: true,
},
)
useMenuCloseOnEsc(open)
</script>
@ -417,7 +476,7 @@ useMenuCloseOnEsc(open)
>
<div
v-if="!isPublic && (activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.KANBAN)"
class="flex items-center gap-2 px-2 mb-4"
class="flex items-center gap-2 px-2 mb-4 w-80"
>
<div class="pl-2 flex text-sm select-none text-gray-600">{{ $t('labels.coverImageField') }}</div>
@ -427,7 +486,7 @@ useMenuCloseOnEsc(open)
<a-select
v-model:value="coverImageColumnId"
class="flex-1 max-w-[calc(100%_-_33px)]"
dropdown-class-name="nc-dropdown-cover-image !rounded-lg "
dropdown-class-name="nc-dropdown-cover-image !rounded-lg"
:bordered="false"
@click.stop
>

6
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -236,4 +236,10 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
.nc-lookup-cell .nc-text-area-clamped-text {
@apply !mr-1;
}
.nc-lookup-cell {
&:has(.nc-cell-attachment) {
height: auto !important;
}
}
</style>

Loading…
Cancel
Save