Browse Source

Merge branch 'fix/save-rows-on-tab-switch' into fix/3565-new-row-insert

# Conflicts:
#	packages/nc-gui/components/smartsheet/Row.vue
#	packages/nc-gui/composables/useSmartsheetRowStore.ts
#	packages/nc-gui/composables/useViewData.ts
pull/3569/head
Pranav C 2 years ago
parent
commit
3d1efe1087
  1. 14
      packages/nc-gui/components/smartsheet/Cell.vue
  2. 367
      packages/nc-gui/components/smartsheet/Grid.vue
  3. 7
      packages/nc-gui/components/smartsheet/Row.vue
  4. 15
      packages/nc-gui/components/tabs/Smartsheet.vue
  5. 3
      packages/nc-gui/composables/useSmartsheetRowStore.ts
  6. 24
      packages/nc-gui/composables/useViewData.ts
  7. 10
      packages/nc-gui/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue

14
packages/nc-gui/components/smartsheet/Cell.vue

@ -17,6 +17,7 @@ import {
useDebounceFn, useDebounceFn,
useVModel, useVModel,
} from '#imports' } from '#imports'
import { useSmartsheetRowStoreOrThrow } from '~/composables/useSmartsheetRowStore'
import { NavigateDir } from '~/lib' import { NavigateDir } from '~/lib'
interface Props { interface Props {
@ -57,13 +58,14 @@ const isPublic = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false)) const isLocked = inject(IsLockedInj, ref(false))
let changed = $ref(false) const { currentRow } = useSmartsheetRowStoreOrThrow()
const syncValue = useDebounceFn(function () { const syncValue = useDebounceFn(function () {
changed = false currentRow.value.rowMeta.changed = false
emit('save') emit('save')
}, 1000) }, 1000)
const isAutoSaved = $computed(() => { const isAutoSaved = $computed(() => {
return [ return [
UITypes.SingleLineText, UITypes.SingleLineText,
@ -89,13 +91,13 @@ const vModel = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (val) => { set: (val) => {
if (val !== props.modelValue) { if (val !== props.modelValue) {
changed = true currentRow.value.rowMeta.changed = true
emit('update:modelValue', val) emit('update:modelValue', val)
if (isAutoSaved) { if (isAutoSaved) {
syncValue() syncValue()
} else if (!isManualSaved) { } else if (!isManualSaved) {
emit('save') emit('save')
changed = true currentRow.value.rowMeta.changed = true
} }
} }
}, },
@ -129,9 +131,9 @@ const {
const syncAndNavigate = (dir: NavigateDir) => { const syncAndNavigate = (dir: NavigateDir) => {
if (isJSON.value) return if (isJSON.value) return
if (changed) { if (currentRow.value.rowMeta.changed) {
emit('save') emit('save')
changed = false currentRow.value.rowMeta.changed = false
} }
emit('navigate', dir) emit('navigate', dir)
} }

367
packages/nc-gui/components/smartsheet/Grid.vue

@ -248,36 +248,35 @@ const onKeyDown = async (e: KeyboardEvent) => {
e.preventDefault() e.preventDefault()
if (selected.row < data.value.length - 1) selected.row++ if (selected.row < data.value.length - 1) selected.row++
break break
default: default: {
{ const rowObj = data.value[selected.row]
const rowObj = data.value[selected.row] const columnObj = fields.value[selected.col]
const columnObj = fields.value[selected.col]
if ((!editEnabled && e.metaKey) || e.ctrlKey) {
if ((!editEnabled && e.metaKey) || e.ctrlKey) { switch (e.keyCode) {
switch (e.keyCode) { // copy - ctrl/cmd +c
// copy - ctrl/cmd +c case 67:
case 67: await copy(rowObj.row[columnObj.title] || '')
await copy(rowObj.row[columnObj.title] || '') break
break
}
} }
}
if (editEnabled || e.ctrlKey || e.altKey || e.metaKey) { if (editEnabled || e.ctrlKey || e.altKey || e.metaKey) {
return return
} }
/** on letter key press make cell editable and empty */ /** on letter key press make cell editable and empty */
if (e?.key?.length === 1) { if (e?.key?.length === 1) {
if (!isPkAvail && !rowObj.rowMeta.new) { if (!isPkAvail && !rowObj.rowMeta.new) {
// Update not allowed for table which doesn't have primary Key // Update not allowed for table which doesn't have primary Key
return message.info(t('msg.info.updateNotAllowedWithoutPK')) return message.info(t('msg.info.updateNotAllowedWithoutPK'))
} }
if (makeEditable(rowObj, columnObj)) { if (makeEditable(rowObj, columnObj)) {
rowObj.row[columnObj.title] = '' rowObj.row[columnObj.title] = ''
}
// editEnabled = true
} }
// editEnabled = true
} }
}
break break
} }
} }
@ -324,6 +323,31 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
contextMenuTarget.value = target contextMenuTarget.value = target
} }
} }
const rowRefs = $ref<any[]>()
onBeforeUnmount(async () => {
console.log(rowRefs)
for (const [index, currentRow] of Object.entries(data.value)) {
if (currentRow.rowMeta.new) {
const syncLTARRefs = rowRefs[index]!.syncLTARRefs
const savedRow = await updateOrSaveRow(currentRow, null)
await syncLTARRefs(savedRow)
currentRow.rowMeta.changed = false
continue
}
if (currentRow.rowMeta.changed) {
currentRow.rowMeta.changed = false
for (const field of meta?.value.columns ?? []) {
if (isVirtualCol(field)) continue
if (currentRow.row[field.title!] !== currentRow.oldRow[field.title!]) {
await updateOrSaveRow(currentRow, field.title!)
}
}
}
}
})
</script> </script>
<template> <template>
@ -339,87 +363,88 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
@contextmenu="showContextMenu" @contextmenu="showContextMenu"
> >
<thead> <thead>
<tr class="nc-grid-header border-1 bg-gray-100 sticky top[-1px]"> <tr class="nc-grid-header border-1 bg-gray-100 sticky top[-1px]">
<th> <th>
<div class="w-full h-full bg-gray-100 flex min-w-[70px] pl-5 pr-1 items-center"> <div class="w-full h-full bg-gray-100 flex min-w-[70px] pl-5 pr-1 items-center">
<template v-if="!readOnly"> <template v-if="!readOnly">
<div class="nc-no-label text-gray-500" :class="{ hidden: selectedAllRecords }">#</div> <div class="nc-no-label text-gray-500" :class="{ hidden: selectedAllRecords }">#</div>
<div <div
:class="{ hidden: !selectedAllRecords, flex: selectedAllRecords }" :class="{ hidden: !selectedAllRecords, flex: selectedAllRecords }"
class="nc-check-all w-full items-center" class="nc-check-all w-full items-center"
> >
<a-checkbox v-model:checked="selectedAllRecords" /> <a-checkbox v-model:checked="selectedAllRecords" />
<span class="flex-1" /> <span class="flex-1" />
</div>
</template>
<template v-else>
<div class="text-gray-500">#</div>
</template>
</div>
</th>
<th
v-for="col in fields"
:key="col.title"
v-xc-ver-resize
:data-col="col.id"
:data-title="col.title"
@xcresize="onresize(col.id, $event)"
@xcresizing="onXcResizing(col.title, $event)"
@xcresized="resizingCol = null"
>
<div class="w-full h-full bg-gray-100 flex items-center">
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="readOnly" />
<SmartsheetHeaderCell v-else :column="col" :hide-menu="readOnly" />
</div>
</th>
<th
v-if="!readOnly && !isLocked && isUIAllowed('add-column') && !isSqlView"
v-t="['c:column:add']"
class="cursor-pointer"
@click.stop="addColumnDropdown = true"
>
<a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']">
<div class="h-full w-[60px] flex items-center justify-center">
<MdiPlus class="text-sm nc-column-add" />
</div> </div>
</template>
<template v-else>
<div class="text-gray-500">#</div>
</template>
</div>
</th>
<th
v-for="col in fields"
:key="col.title"
v-xc-ver-resize
:data-col="col.id"
:data-title="col.title"
@xcresize="onresize(col.id, $event)"
@xcresizing="onXcResizing(col.title, $event)"
@xcresized="resizingCol = null"
>
<div class="w-full h-full bg-gray-100 flex items-center">
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="readOnly" />
<SmartsheetHeaderCell v-else :column="col" :hide-menu="readOnly" />
</div>
</th>
<th
v-if="!readOnly && !isLocked && isUIAllowed('add-column') && !isSqlView"
v-t="['c:column:add']"
class="cursor-pointer"
@click.stop="addColumnDropdown = true"
>
<a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']">
<div class="h-full w-[60px] flex items-center justify-center">
<MdiPlus class="text-sm nc-column-add" />
</div>
<template #overlay> <template #overlay>
<SmartsheetColumnEditOrAddProvider <SmartsheetColumnEditOrAddProvider
v-if="addColumnDropdown" v-if="addColumnDropdown"
@submit="addColumnDropdown = false" @submit="addColumnDropdown = false"
@cancel="addColumnDropdown = false" @cancel="addColumnDropdown = false"
@click.stop @click.stop
@keydown.stop @keydown.stop
/> />
</template> </template>
</a-dropdown> </a-dropdown>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<SmartsheetRow v-for="(row, rowIndex) of data" :key="rowIndex" :row="row"> <SmartsheetRow ref="rowRefs" v-for="(row, rowIndex) of data" :key="rowIndex" :row="row">
<template #default="{ state }"> <template #default="{ state }">
<tr class="nc-grid-row"> <tr class="nc-grid-row">
<td key="row-index" class="caption nc-grid-cell pl-5 pr-1"> <td key="row-index" class="caption nc-grid-cell pl-5 pr-1">
<div class="items-center flex gap-1 min-w-[55px]"> <div class="items-center flex gap-1 min-w-[55px]">
<div <div
v-if="!readOnly || !isLocked" v-if="!readOnly || !isLocked"
class="nc-row-no text-xs text-gray-500" class="nc-row-no text-xs text-gray-500"
:class="{ toggle: !readOnly, hidden: row.rowMeta.selected }" :class="{ toggle: !readOnly, hidden: row.rowMeta.selected }"
> >
{{ rowIndex + 1 }} {{ rowIndex + 1 }}
</div> </div>
<div <div
v-if="!readOnly" v-if="!readOnly"
:class="{ hidden: !row.rowMeta.selected, flex: row.rowMeta.selected }" :class="{ hidden: !row.rowMeta.selected, flex: row.rowMeta.selected }"
class="nc-row-expand-and-checkbox" class="nc-row-expand-and-checkbox"
> >
<a-checkbox v-model:checked="row.rowMeta.selected" /> <a-checkbox v-model:checked="row.rowMeta.selected" />
</div> </div>
<span class="flex-1" /> <span class="flex-1" />
<div v-if="!readOnly && !isLocked" class="nc-expand" :class="{ 'nc-comment': row.rowMeta?.commentCount }"> <div v-if="!readOnly && !isLocked" class="nc-expand"
:class="{ 'nc-comment': row.rowMeta?.commentCount }">
<span <span
v-if="row.rowMeta?.commentCount" v-if="row.rowMeta?.commentCount"
class="py-1 px-3 rounded-full text-xs cursor-pointer select-none transform hover:(scale-110)" class="py-1 px-3 rounded-full text-xs cursor-pointer select-none transform hover:(scale-110)"
@ -428,87 +453,87 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
> >
{{ row.rowMeta.commentCount }} {{ row.rowMeta.commentCount }}
</span> </span>
<div <div
v-else v-else
class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)" class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)"
> >
<MdiArrowExpand <MdiArrowExpand
v-t="['c:row-expand']" v-t="['c:row-expand']"
class="select-none transform hover:(text-accent scale-120) nc-row-expand" class="select-none transform hover:(text-accent scale-120) nc-row-expand"
@click="expandForm(row, state)" @click="expandForm(row, state)"
/> />
</div>
</div> </div>
</div> </div>
</td> </div>
<td </td>
v-for="(columnObj, colIndex) of fields" <td
:ref="cellRefs.set" v-for="(columnObj, colIndex) of fields"
:key="columnObj.id" :ref="cellRefs.set"
class="cell relative cursor-pointer nc-grid-cell" :key="columnObj.id"
:class="{ class="cell relative cursor-pointer nc-grid-cell"
:class="{
active: isUIAllowed('xcDatatableEditable') && selected.col === colIndex && selected.row === rowIndex, active: isUIAllowed('xcDatatableEditable') && selected.col === colIndex && selected.row === rowIndex,
}" }"
:data-key="rowIndex + columnObj.id" :data-key="rowIndex + columnObj.id"
:data-col="columnObj.id" :data-col="columnObj.id"
:data-title="columnObj.title" :data-title="columnObj.title"
@click="selectCell(rowIndex, colIndex)" @click="selectCell(rowIndex, colIndex)"
@dblclick="makeEditable(row, columnObj)" @dblclick="makeEditable(row, columnObj)"
@contextmenu="showContextMenu($event, { row: rowIndex, col: colIndex })" @contextmenu="showContextMenu($event, { row: rowIndex, col: colIndex })"
> >
<div class="w-full h-full"> <div class="w-full h-full">
<SmartsheetVirtualCell <SmartsheetVirtualCell
v-if="isVirtualCol(columnObj)" v-if="isVirtualCol(columnObj)"
v-model="row.row[columnObj.title]" v-model="row.row[columnObj.title]"
:column="columnObj" :column="columnObj"
:active="selected.col === colIndex && selected.row === rowIndex" :active="selected.col === colIndex && selected.row === rowIndex"
:row="row" :row="row"
@navigate="onNavigate" @navigate="onNavigate"
/> />
<SmartsheetCell <SmartsheetCell
v-else v-else
v-model="row.row[columnObj.title]" v-model="row.row[columnObj.title]"
:column="columnObj" :column="columnObj"
:edit-enabled=" :edit-enabled="
isUIAllowed('xcDatatableEditable') && isUIAllowed('xcDatatableEditable') &&
editEnabled && editEnabled &&
selected.col === colIndex && selected.col === colIndex &&
selected.row === rowIndex selected.row === rowIndex
" "
:row-index="rowIndex" :row-index="rowIndex"
:active="selected.col === colIndex && selected.row === rowIndex" :active="selected.col === colIndex && selected.row === rowIndex"
@update:edit-enabled="editEnabled = false" @update:edit-enabled="editEnabled = false"
@save="updateOrSaveRow(row, columnObj.title)" @save="updateOrSaveRow(row, columnObj.title)"
@navigate="onNavigate" @navigate="onNavigate"
@cancel="editEnabled = false" @cancel="editEnabled = false"
/> />
</div> </div>
</td> </td>
</tr> </tr>
</template> </template>
</SmartsheetRow> </SmartsheetRow>
<!-- <!--
TODO: add relationType !== 'bt' ? TODO: add relationType !== 'bt' ?
v1: <tr v-if="!isView && !isLocked && !isPublicView && isEditable && relationType !== 'bt'"> v1: <tr v-if="!isView && !isLocked && !isPublicView && isEditable && relationType !== 'bt'">
--> -->
<tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView"> <tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView">
<td <td
v-t="['c:row:add:grid-bottom']" v-t="['c:row:add:grid-bottom']"
:colspan="visibleColLength + 1" :colspan="visibleColLength + 1"
class="text-left pointer nc-grid-add-new-cell cursor-pointer" class="text-left pointer nc-grid-add-new-cell cursor-pointer"
@click="addEmptyRow()" @click="addEmptyRow()"
> >
<div class="px-2 w-full flex items-center text-gray-500"> <div class="px-2 w-full flex items-center text-gray-500">
<MdiPlus class="text-pint-500 text-xs ml-2 text-primary" /> <MdiPlus class="text-pint-500 text-xs ml-2 text-primary" />
<span class="ml-1"> <span class="ml-1">
{{ $t('activity.addRow') }} {{ $t('activity.addRow') }}
</span> </span>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

7
packages/nc-gui/components/smartsheet/Row.vue

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Row } from '~/composables' import type { Row } from '~/composables'
import { useProvideSmartsheetRowStore, useSmartsheetStoreOrThrow } from '#imports' import { ReloadRowDataHookInj, useProvideSmartsheetRowStore, useSmartsheetStoreOrThrow } from '#imports'
import { ReloadRowDataHookInj } from '~/context'
interface Props { interface Props {
row: Row row: Row
@ -34,6 +33,10 @@ reloadHook.on(() => {
}) })
provide(ReloadRowDataHookInj, reloadHook) provide(ReloadRowDataHookInj, reloadHook)
defineExpose({
syncLTARRefs
})
</script> </script>
<template> <template>

15
packages/nc-gui/components/tabs/Smartsheet.vue

@ -21,6 +21,11 @@ import {
import type { TabItem } from '~/composables' import type { TabItem } from '~/composables'
const { activeTab } = defineProps<{
activeTab: TabItem
}>()
const { metas } = useMetas() const { metas } = useMetas()
const activeView = ref() const activeView = ref()
@ -29,11 +34,15 @@ const el = ref<typeof SmartsheetGrid>()
const fields = ref<ColumnType[]>([]) const fields = ref<ColumnType[]>([])
const tabMeta = inject( // const tabMeta = inject(
// TabMetaInj,
// computed(() => ({} as TabItem)),
// )
provide(
TabMetaInj, TabMetaInj,
computed(() => ({} as TabItem)), ref(activeTab),
) )
const meta = computed<TableType>(() => metas.value?.[tabMeta?.value?.id as string]) const meta = computed<TableType>(() => metas.value?.[activeTab?.id as string])
const reloadEventHook = createEventHook<void>() const reloadEventHook = createEventHook<void>()
const openNewRecordFormHook = createEventHook<void>() const openNewRecordFormHook = createEventHook<void>()

3
packages/nc-gui/composables/useSmartsheetRowStore.ts

@ -29,6 +29,8 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
const { metas } = useMetas() const { metas } = useMetas()
const currentRow = ref(row)
// state // state
const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({}) const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({})
@ -132,6 +134,7 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
removeLTARRef, removeLTARRef,
syncLTARRefs, syncLTARRefs,
loadRow, loadRow,
currentRow
} }
}, 'smartsheet-row-store') }, 'smartsheet-row-store')

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

@ -28,6 +28,7 @@ export interface Row {
new?: boolean new?: boolean
selected?: boolean selected?: boolean
commentCount?: number commentCount?: number
changed?: boolean
} }
} }
@ -162,16 +163,15 @@ export function useViewData(
} }
async function loadData(params: Parameters<Api<any>['dbViewRow']['list']>[4] = {}) { async function loadData(params: Parameters<Api<any>['dbViewRow']['list']>[4] = {}) {
console.log(queryParams.value)
if ((!project?.value?.id || !meta?.value?.id || !viewMeta?.value?.id) && !isPublic.value) return if ((!project?.value?.id || !meta?.value?.id || !viewMeta?.value?.id) && !isPublic.value) return
const response = !isPublic.value const response = !isPublic.value
? await api.dbViewRow.list('noco', project.value.id!, meta!.value.id!, viewMeta!.value.id, { ? await api.dbViewRow.list('noco', project.value.id!, meta!.value.id!, viewMeta!.value.id, {
...queryParams.value, ...queryParams.value,
...params, ...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: where?.value, where: where?.value,
}) })
: await fetchSharedViewData() : await fetchSharedViewData()
formattedData.value = formatData(response.list) formattedData.value = formatData(response.list)
paginationData.value = response.pageInfo paginationData.value = response.pageInfo
@ -253,7 +253,7 @@ export function useViewData(
async function updateOrSaveRow(row: Row, property: string) { async function updateOrSaveRow(row: Row, property: string) {
if (row.rowMeta.new) { if (row.rowMeta.new) {
await insertRow(row.row, formattedData.value.indexOf(row)) return await insertRow(row.row, formattedData.value.indexOf(row))
} else { } else {
await updateRowProperty(row, property) await updateRowProperty(row, property)
} }
@ -280,11 +280,9 @@ export function useViewData(
if (res.message) { if (res.message) {
message.info( message.info(
`Row delete failed: ${h('div', { `Row delete failed: ${`Unable to delete row with ID ${id} because of the following:
innerHTML: `<div style="padding:10px 4px">Unable to delete row with ID ${id} because of the following: \n${res.message.join('\n')}.\n
<br><br>${res.message.join('<br>')}<br><br> Clear the data first & try again`})}`,
Clear the data first & try again</div>`,
})}`,
) )
return false return false
} }

10
packages/nc-gui/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue

@ -1,8 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { TabItem } from '~/composables'
import { TabMetaInj } from '~/context'
const { getMeta } = useMetas() const { getMeta } = useMetas()
const route = useRoute() const route = useRoute()
const loading = ref(true) const loading = ref(true)
const activeTab = inject(
TabMetaInj,
computed(() => ({} as TabItem)),
)
getMeta(route.params.title as string, true).finally(() => { getMeta(route.params.title as string, true).finally(() => {
loading.value = false loading.value = false
}) })
@ -12,7 +20,7 @@ getMeta(route.params.title as string, true).finally(() => {
<div v-if="loading" class="flex items-center justify-center h-full w-full"> <div v-if="loading" class="flex items-center justify-center h-full w-full">
<a-spin size="large" /> <a-spin size="large" />
</div> </div>
<TabsSmartsheet v-else /> <TabsSmartsheet :active-tab="activeTab" :key="route.params.title" v-else />
</template> </template>
<style scoped></style> <style scoped></style>

Loading…
Cancel
Save