Browse Source

add delete and duplicate row buttons to expanded form and grid view

pull/5035/head
Daniel Spaude 2 years ago
parent
commit
04d9b08f55
No known key found for this signature in database
GPG Key ID: 654A3D1FA4F35FFE
  1. 191
      packages/nc-gui/components/smartsheet/Gallery.vue
  2. 50
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  3. 29
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  4. 1
      packages/nc-gui/composables/useViewData.ts
  5. 2
      packages/nc-gui/lang/en.json

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

@ -49,6 +49,7 @@ const {
galleryData,
changePage,
addEmptyRow,
deleteRow,
navigateToSiblingRow,
} = useViewData(meta, view)
@ -81,6 +82,9 @@ const isRowEmpty = (record: any, col: any) => {
return Array.isArray(val) && val.length === 0
}
// const { xWhere, isPkAvail, isSqlView, eventBus } = useSmartsheetStoreOrThrow()
const { isSqlView } = useSmartsheetStoreOrThrow()
const attachments = (record: any): Attachment[] => {
try {
if (coverImageColumn?.title && record.row[coverImageColumn.title]) {
@ -168,85 +172,134 @@ watch(view, async (nextView) => {
await loadGalleryData()
}
})
const { isUIAllowed } = useUIPermission()
const hasEditPermission = $computed(() => isUIAllowed('xcDatatableEditable'))
// TODO: extract this code (which is duplicated in grid and gallery) into a separate component
const _contextMenu = ref(false)
const contextMenu = computed({
get: () => _contextMenu.value,
set: (val) => {
if (hasEditPermission) {
_contextMenu.value = val
}
},
})
const contextMenuTarget = ref<{ row: number } | null>(null)
const showContextMenu = (e: MouseEvent, target?: { row: number }) => {
if (isSqlView.value) return
e.preventDefault()
if (target) {
contextMenuTarget.value = target
}
}
</script>
<template>
<div class="flex flex-col h-full w-full overflow-auto nc-gallery" data-testid="nc-gallery-wrapper">
<div class="nc-gallery-container grid gap-2 my-4 px-3">
<div v-for="record in data" :key="`record-${record.row.id}`">
<LazySmartsheetRow :row="record">
<a-card
hoverable
class="!rounded-lg h-full overflow-hidden break-all max-w-[450px]"
:data-testid="`nc-gallery-card-${record.row.id}`"
@click="expandFormClick($event, record)"
>
<template v-if="galleryData?.fk_cover_image_col_id" #cover>
<a-carousel v-if="!reloadAttachments && attachments(record).length" autoplay class="gallery-carousel" arrows>
<template #customPaging>
<a>
<div class="pt-[12px]">
<div></div>
</div>
</a>
</template>
<template #prevArrow>
<div style="z-index: 1"></div>
</template>
<template #nextArrow>
<div style="z-index: 1"></div>
</template>
<LazyNuxtImg
v-for="(attachment, index) in attachments(record)"
:key="`carousel-${record.row.id}-${index}`"
quality="90"
placeholder
class="h-52 object-contain"
:src="attachment.url"
/>
</a-carousel>
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template>
<div v-for="col in fieldsWithoutCover" :key="`record-${record.row.id}-${col.id}`">
<div
v-if="!isRowEmpty(record, col) || isLTAR(col.uidt)"
class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full"
>
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5">
<div class="w-full text-gray-600">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div>
</div>
<a-dropdown
v-model:visible="contextMenu"
:trigger="isSqlView ? [] : ['contextmenu']"
overlay-class-name="nc-dropdown-grid-context-menu"
>
<template #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)">
<div v-e="['a:row:delete']" class="nc-project-menu-item">
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</div>
</a-menu-item>
<div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start">
<LazySmartsheetVirtualCell
v-if="isVirtualCol(col)"
v-model="record.row[col.title]"
:column="col"
:row="record"
<a-menu-item v-if="contextMenuTarget" @click="openNewRecordFormHook.trigger()">
<div v-e="['a:row:insert']" class="nc-project-menu-item">
<!-- Insert New Row -->
{{ $t('activity.insertRow') }}
</div>
</a-menu-item>
</a-menu>
</template>
<div class="nc-gallery-container grid gap-2 my-4 px-3">
<!-- v-for="(row, rowIndex) of data -->
<div v-for="(record, rowIndex) in data" :key="`record-${record.row.id}`">
<LazySmartsheetRow :row="record">
<a-card
hoverable
class="!rounded-lg h-full overflow-hidden break-all max-w-[450px]"
:data-testid="`nc-gallery-card-${record.row.id}`"
@click="expandFormClick($event, record)"
@contextmenu="showContextMenu($event, { row: rowIndex })"
>
<template v-if="galleryData?.fk_cover_image_col_id" #cover>
<a-carousel v-if="!reloadAttachments && attachments(record).length" autoplay class="gallery-carousel" arrows>
<template #customPaging>
<a>
<div class="pt-[12px]">
<div></div>
</div>
</a>
</template>
<template #prevArrow>
<div style="z-index: 1"></div>
</template>
<template #nextArrow>
<div style="z-index: 1"></div>
</template>
<LazyNuxtImg
v-for="(attachment, index) in attachments(record)"
:key="`carousel-${record.row.id}-${index}`"
quality="90"
placeholder
class="h-52 object-contain"
:src="attachment.url"
/>
</a-carousel>
<LazySmartsheetCell
v-else
v-model="record.row[col.title]"
:column="col"
:edit-enabled="false"
:read-only="true"
/>
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template>
<div v-for="col in fieldsWithoutCover" :key="`record-${record.row.id}-${col.id}`">
<div
v-if="!isRowEmpty(record, col) || isLTAR(col.uidt)"
class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full"
>
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5">
<div class="w-full text-gray-600">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div>
</div>
<div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start">
<LazySmartsheetVirtualCell
v-if="isVirtualCol(col)"
v-model="record.row[col.title]"
:column="col"
:row="record"
/>
<LazySmartsheetCell
v-else
v-model="record.row[col.title]"
:column="col"
:edit-enabled="false"
:read-only="true"
/>
</div>
</div>
</div>
</div>
</a-card>
</LazySmartsheetRow>
</a-card>
</LazySmartsheetRow>
</div>
</div>
</div>
</a-dropdown>
<div class="flex-1" />

50
packages/nc-gui/components/smartsheet/expanded-form/Header.vue

@ -12,7 +12,7 @@ import {
const props = defineProps<{ view?: ViewType }>()
const emit = defineEmits(['cancel'])
const emit = defineEmits(['cancel', 'duplicateRow'])
const route = useRoute()
@ -72,6 +72,23 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
}
}
})
const showDeleteRowModal = ref(false)
const { deleteRowById } = useViewData(meta, ref(props.view))
const onDeleteRowClick = () => {
showDeleteRowModal.value = true
}
const onConfirmDeleteRowClick = async () => {
showDeleteRowModal.value = false
await deleteRowById(primaryKey.value)
reloadTrigger.trigger()
emit('cancel')
message.success('Row deleted')
}
</script>
<template>
@ -127,6 +144,37 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
</div>
</a-button>
<a-dropdown>
<a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-1 items-center">
<!-- More -->
<span class="!text-sm font-weight-medium">{{ $t('general.more') }}</span>
<MdiMenuDown class="text-grey" />
</div>
</a-button>
<template #overlay>
<div class="bg-gray-50 py-2 shadow-lg !border">
<div>
<div v-e="['a:actions:duplicate-row']" class="nc-menu-item" @click="emit('duplicateRow')">
<MdiContentCopy class="text-gray-500" />
{{ $t('activity.duplicateRow') }}
</div>
<a-modal v-model:visible="showDeleteRowModal" title="Delete row?" @ok="onConfirmDeleteRowClick">
<p>Are you sure you want to delete this row?</p>
</a-modal>
<div v-e="['a:actions:delete-row']" class="nc-menu-item" @click="onDeleteRowClick" disabled="true">
<MdiDelete class="text-gray-500" />
{{ $t('activity.deleteRow') }}
</div>
</div>
</div>
</template>
</a-dropdown>
<a-dropdown-button class="nc-expand-form-save-btn" type="primary" :disabled="!isUIAllowed('tableRowUpdate')" @click="save">
<template #overlay>
<a-menu class="nc-expand-form-save-dropdown-menu">

29
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -39,6 +39,8 @@ const props = defineProps<Props>()
const emits = defineEmits(['update:modelValue', 'cancel', 'next', 'prev'])
const { t } = useI18n()
const row = ref(props.row)
const state = toRef(props, 'state')
@ -60,6 +62,8 @@ provide(MetaInj, meta)
const { commentsDrawer, changedColumns, state: rowState, isNew, loadRow } = useProvideExpandedFormStore(meta, row)
const duplicatingRowInProgress = ref(false)
if (props.loadRow) {
await loadRow()
}
@ -101,6 +105,23 @@ const onClose = () => {
isExpanded.value = false
}
const onDuplicateRow = () => {
duplicatingRowInProgress.value = true
const newRow = Object.assign(
{},
{
row: row.value.row,
oldRow: {},
rowMeta: { new: true },
},
)
setTimeout(async () => {
row.value = newRow
duplicatingRowInProgress.value = false
message.success(t('msg.success.rowDuplicatedWithoutSavedYet'))
}, 500)
}
const reloadParentRowHook = inject(ReloadRowDataHookInj, createEventHook())
// override reload trigger and use it to reload grid and the form itself
@ -148,7 +169,7 @@ export default {
class="nc-drawer-expanded-form"
:class="{ active: isExpanded }"
>
<SmartsheetExpandedFormHeader :view="props.view" @cancel="onClose" />
<SmartsheetExpandedFormHeader :view="props.view" @cancel="onClose" @duplicateRow="onDuplicateRow" />
<div class="!bg-gray-100 rounded flex-1 relative">
<template v-if="props.showNextPrevIcons">
@ -169,8 +190,10 @@ export default {
<div class="flex h-full nc-form-wrapper items-stretch min-h-[max(70vh,100%)]">
<div class="flex-1 overflow-auto scrollbar-thin-dull nc-form-fields-container">
<div class="w-[500px] mx-auto">
<a-spin v-if="duplicatingRowInProgress" class="!flex items-center" />
<div
v-for="(col, i) of fields"
v-if="!duplicatingRowInProgress"
v-show="!isVirtualCol(col) || !isNew || col.uidt === UITypes.LinkToAnotherRecord"
:key="col.title"
class="mt-2 py-2"
@ -235,11 +258,13 @@ export default {
.nc-prev-arrow,
.nc-next-arrow {
@apply absolute opacity-70 rounded-full transition-transform transition-background transition-opacity transform bg-white hover:(bg-gray-200) active:(scale-125 opacity-100) text-xl;
@apply absolute opacity-70 rounded-full transition-transform transition-background transition-opacity transform bg-white hover: (bg-gray-200) active:(scale-125 opacity-100) text-xl;
}
.nc-prev-arrow {
@apply left-4 top-4;
}
.nc-next-arrow {
@apply right-4 top-4;
}

1
packages/nc-gui/composables/useViewData.ts

@ -603,5 +603,6 @@ export function useViewData(
removeLastEmptyRow,
removeRowIfNew,
navigateToSiblingRow,
deleteRowById,
}
}

2
packages/nc-gui/lang/en.json

@ -387,6 +387,7 @@
"saveAndStay": "Save & Stay",
"insertRow": "Insert New Row",
"deleteRow": "Delete Row",
"duplicateRow": "Duplicate Row",
"deleteSelectedRow": "Delete Selected Rows",
"importExcel": "Import Excel",
"importCSV": "Import CSV",
@ -716,6 +717,7 @@
},
"success": {
"columnDuplicated": "Column duplicated successfully",
"rowDuplicatedWithoutSavedYet": "Row duplicated (not saved)",
"updatedUIACL": "Updated UI ACL for tables successfully",
"pluginUninstalled": "Plugin uninstalled successfully",
"pluginSettingsSaved": "Plugin settings saved successfully",

Loading…
Cancel
Save