Browse Source

Merge pull request #3169 from nocodb/fix/3161-form-view-issues

fix(gui-v2): show form items in editable state and handle LTAR columns
pull/3179/head
Raju Udava 2 years ago committed by GitHub
parent
commit
73bcb09be7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      packages/nc-gui-v2/components/cell/DatePicker.vue
  2. 12
      packages/nc-gui-v2/components/cell/DateTimePicker.vue
  3. 17
      packages/nc-gui-v2/components/cell/Json.vue
  4. 9
      packages/nc-gui-v2/components/cell/TimePicker.vue
  5. 10
      packages/nc-gui-v2/components/cell/YearPicker.vue
  6. 19
      packages/nc-gui-v2/components/smartsheet/Form.vue
  7. 6
      packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue
  8. 6
      packages/nc-gui-v2/components/virtual-cell/HasMany.vue
  9. 6
      packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue
  10. 8
      packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue
  11. 30
      packages/nc-gui-v2/components/virtual-cell/components/ListChildItems.vue
  12. 8
      packages/nc-gui-v2/components/virtual-cell/components/ListItems.vue
  13. 7
      packages/nc-gui-v2/composables/useLTARStore.ts
  14. 7
      packages/nc-gui-v2/composables/useSmartsheetRowStore.ts
  15. 1
      packages/nc-gui-v2/composables/useViewData.ts
  16. 2
      packages/nc-gui-v2/context/index.ts

10
packages/nc-gui-v2/components/cell/DatePicker.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { ColumnInj, EditModeInj, computed, inject, ref, watch } from '#imports'
import { ColumnInj, ReadonlyInj, computed, inject, ref, watch } from '#imports'
interface Props {
modelValue?: string | null
@ -12,7 +12,7 @@ const emit = defineEmits(['update:modelValue'])
const columnMeta = inject(ColumnInj, null)!
const editEnabled = inject(EditModeInj)!
const readOnly = inject(ReadonlyInj, false)
let isDateInvalid = $ref(false)
@ -55,7 +55,7 @@ watch(
{ flush: 'post' },
)
const placeholder = computed(() => (isDateInvalid ? 'Invalid date' : editEnabled.value ? 'Select date' : ''))
const placeholder = computed(() => (isDateInvalid ? 'Invalid date' : readOnly ? 'Select date' : ''))
</script>
<template>
@ -65,10 +65,10 @@ const placeholder = computed(() => (isDateInvalid ? 'Invalid date' : editEnabled
class="!w-full px-1"
:format="dateFormat"
:placeholder="placeholder"
:allow-clear="!editEnabled"
:allow-clear="!readOnly"
:input-read-only="true"
:dropdown-class-name="randomClass"
:open="editEnabled ? false : open"
:open="readOnly ? false : open"
@click="open = !open"
>
<template #suffixIcon></template>

12
packages/nc-gui-v2/components/cell/DateTimePicker.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { EditModeInj } from '~/context'
import { ReadonlyInj } from '#imports'
interface Props {
modelValue: string | null | undefined
@ -12,7 +12,7 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject()
const editEnabled = inject(EditModeInj)
const readOnly = inject(ReadonlyInj, false)
let isDateInvalid = $ref(false)
@ -64,12 +64,12 @@ watch(
:bordered="false"
class="!w-full px-1"
format="YYYY-MM-DD HH:mm"
:placeholder="isDateInvalid ? 'Invalid date' : !editEnabled ? 'Select date and time' : ''"
:allow-clear="!editEnabled"
:placeholder="isDateInvalid ? 'Invalid date' : !readOnly ? 'Select date and time' : ''"
:allow-clear="!readOnly"
:input-read-only="true"
:dropdown-class-name="randomClass"
:open="editEnabled ? false : open"
:disabled="!editEnabled"
:open="readOnly ? false : open"
:disabled="readOnly"
@click="open = !open"
@ok="open = !open"
>

17
packages/nc-gui-v2/components/cell/Json.vue

@ -2,7 +2,7 @@
import { Modal as AModal } from 'ant-design-vue'
import Editor from '~/components/monaco/Editor.vue'
import { ReadonlyInj, computed, inject, ref, useVModel, watch } from '#imports'
import { EditModeInj } from '~/context'
import { EditModeInj, IsFormInj } from '~/context'
interface Props {
modelValue: string | Record<string, any> | undefined
@ -18,22 +18,29 @@ const emits = defineEmits<Emits>()
const editEnabled = inject(EditModeInj, ref(false))
const isForm = inject(IsFormInj, ref(false))
const readonly = inject(ReadonlyInj)
const vModel = useVModel(props, 'modelValue', emits)
const localValueState = ref<string | undefined>()
let error = $ref<string | undefined>()
let isExpanded = $ref(false)
const localValue = computed<string | Record<string, any> | undefined>({
get: () => localValueState.value,
set: (val: undefined | string | Record<string, any>) => {
localValueState.value = typeof val === 'object' ? JSON.stringify(val, null, 2) : val
/** if form and not expanded then sync directly */
if (isForm.value && !isExpanded) {
vModel.value = val
}
},
})
let error = $ref<string | undefined>()
let isExpanded = $ref(false)
const clear = () => {
error = undefined
@ -98,7 +105,7 @@ watch(editEnabled, () => {
<CilFullscreen v-else class="h-2.5" />
</a-button>
<div class="flex flex-row">
<div v-if="!isForm || isExpanded" class="flex flex-row">
<a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel">

9
packages/nc-gui-v2/components/cell/TimePicker.vue

@ -1,7 +1,6 @@
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import dayjs from 'dayjs'
import { EditModeInj } from '~/context'
interface Props {
modelValue?: string | null | undefined
@ -13,7 +12,7 @@ const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject()
const editEnabled = inject(EditModeInj)
const readOnly = inject(ReadonlyInj, false)
let isTimeInvalid = $ref(false)
@ -76,10 +75,10 @@ watch(
use12-hours
format="HH:mm"
class="!w-full px-1"
:placeholder="isTimeInvalid ? 'Invalid time' : !readOnlyMode ? 'Select time' : ''"
:allow-clear="!editEnabled"
:placeholder="isTimeInvalid ? 'Invalid time' : !readOnly ? 'Select time' : ''"
:allow-clear="!readOnly"
:input-read-only="true"
:open="editEnabled ? false : open"
:open="readOnly ? false : open"
:popup-class-name="randomClass"
@click="open = !open"
@ok="open = !open"

10
packages/nc-gui-v2/components/cell/YearPicker.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { EditModeInj, computed, inject, onClickOutside, ref, watch } from '#imports'
import { computed, inject, onClickOutside, ref, watch } from '#imports'
interface Props {
modelValue?: number | string | null
@ -10,7 +10,7 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const editEnabled = inject(EditModeInj)!
const readOnly = inject(ReadonlyInj, false)
let isYearInvalid = $ref(false)
@ -53,7 +53,7 @@ watch(
{ flush: 'post' },
)
const placeholder = computed(() => (isYearInvalid ? 'Invalid year' : editEnabled.value ? 'Select year' : ''))
const placeholder = computed(() => (isYearInvalid ? 'Invalid year' : readOnly ? 'Select year' : ''))
</script>
<template>
@ -63,9 +63,9 @@ const placeholder = computed(() => (isYearInvalid ? 'Invalid year' : editEnabled
:bordered="false"
class="!w-full px-1"
:placeholder="placeholder"
:allow-clear="!editEnabled"
:allow-clear="!readOnly"
:input-read-only="true"
:open="editEnabled ? false : open"
:open="readOnly ? false : open"
:dropdown-class-name="randomClass"
@click="open = !open"
@change="open = !open"

19
packages/nc-gui-v2/components/smartsheet/Form.vue

@ -42,7 +42,7 @@ const secondsRemain = ref(0)
const isEditable = isUIAllowed('editFormView' as Permission)
const meta = inject(MetaInj)
const meta = inject(MetaInj)!
const view = inject(ActiveViewInj)
@ -53,6 +53,15 @@ const { showAll, hideAll, saveOrUpdate } = useViewColumns(view, meta as any, fal
setFormData()
})
const { syncLTARRefs } = useProvideSmartsheetRowStore(
meta,
ref({
row: formState,
oldRow: {},
rowMeta: { new: true },
}),
)
const columns = computed(() => meta?.value?.columns || [])
const localColumns = ref<Record<string, any>[]>([])
@ -92,7 +101,11 @@ async function submitForm() {
return
}
await insertRow(formState)
const insertedRowData = await insertRow(formState)
if (insertedRowData) {
await syncLTARRefs(insertedRowData)
}
submitted.value = true
}
@ -527,6 +540,7 @@ onMounted(async () => {
class="nc-input"
:class="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:column="element"
@click.stop.prevent
/>
</a-form-item>
@ -542,6 +556,7 @@ onMounted(async () => {
:class="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:column="element"
:edit-enabled="true"
@click.stop.prevent
/>
</a-form-item>

6
packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue

@ -5,7 +5,7 @@ import {
ActiveCellInj,
CellValueInj,
ColumnInj,
EditModeInj,
ReadonlyInj,
ReloadViewDataHookInj,
RowInj,
defineAsyncComponent,
@ -31,7 +31,7 @@ const row = inject(RowInj)!
const active = inject(ActiveCellInj)!
const editEnabled = inject(EditModeInj)
const readonly = inject(ReadonlyInj, false)
const listItemsDlg = ref(false)
@ -72,7 +72,7 @@ const unlinkRef = async (rec: Record<string, any>) => {
<ItemChip :item="value" :value="value[relatedTablePrimaryValueProp]" @unlink="unlinkRef(value)" />
</template>
</div>
<div v-if="editEnabled" class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<div v-if="!readonly" class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<component
:is="addIcon"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 select-none group-hover:(text-gray-500)"

6
packages/nc-gui-v2/components/virtual-cell/HasMany.vue

@ -4,8 +4,8 @@ import type { Ref } from 'vue'
import {
CellValueInj,
ColumnInj,
EditModeInj,
IsFormInj,
ReadonlyInj,
ReloadViewDataHookInj,
RowInj,
computed,
@ -32,7 +32,7 @@ const reloadTrigger = inject(ReloadViewDataHookInj)!
const isForm = inject(IsFormInj)
const editEnabled = inject(EditModeInj)
const readonly = inject(ReadonlyInj, false)
const listItemsDlg = ref(false)
@ -94,7 +94,7 @@ const unlinkRef = async (rec: Record<string, any>) => {
@click="childListDlg = true"
/>
<MdiPlus
v-if="editEnabled"
v-if="!readonly"
class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500"
@click="listItemsDlg = true"
/>

6
packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue

@ -4,8 +4,8 @@ import type { Ref } from 'vue'
import {
CellValueInj,
ColumnInj,
EditModeInj,
IsFormInj,
ReadonlyInj,
ReloadViewDataHookInj,
RowInj,
computed,
@ -31,7 +31,7 @@ const reloadTrigger = inject(ReloadViewDataHookInj)!
const isForm = inject(IsFormInj)
const editEnabled = inject(EditModeInj)
const readonly = inject(ReadonlyInj, false)
const listItemsDlg = ref(false)
@ -92,7 +92,7 @@ const unlinkRef = async (rec: Record<string, any>) => {
<MdiArrowExpand class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="childListDlg = true" />
<MdiPlus
v-if="editEnabled"
v-if="!readonly"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500"
@click="listItemsDlg = true"
/>

8
packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ActiveCellInj, EditModeInj, IsFormInj, defineAsyncComponent, inject, ref, useLTARStoreOrThrow } from '#imports'
import { ActiveCellInj, IsFormInj, ReadonlyInj, defineAsyncComponent, inject, ref, useLTARStoreOrThrow } from '#imports'
interface Props {
value?: string | number | boolean
@ -14,7 +14,7 @@ const ExpandedForm: any = defineAsyncComponent(() => import('../../smartsheet/ex
const { relatedTableMeta } = useLTARStoreOrThrow()!
const editEnabled = inject(EditModeInj)!
const readonly = inject(ReadonlyInj, false)
const active = inject(ActiveCellInj, ref(false))
@ -37,13 +37,13 @@ export default {
>
<span class="name">{{ value }}</span>
<div v-show="active || isForm" v-if="editEnabled" class="flex align-center">
<div v-show="active || isForm" v-if="!readonly" class="flex align-center">
<MdiCloseThick class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" @click.stop="emit('unlink')" />
</div>
<Suspense>
<ExpandedForm
v-if="editEnabled"
v-if="!readonly"
v-model="expandedFormDlg"
:row="{ row: item }"
:meta="relatedTableMeta"

30
packages/nc-gui-v2/components/virtual-cell/components/ListChildItems.vue

@ -3,8 +3,8 @@ import { Empty, Modal } from 'ant-design-vue'
import type { ColumnType } from 'nocodb-sdk'
import {
ColumnInj,
EditModeInj,
IsFormInj,
ReadonlyInj,
computed,
useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow,
@ -24,7 +24,7 @@ const isForm = inject(IsFormInj, ref(false))
const column = inject(ColumnInj)
const editEnabled = inject(EditModeInj)
const readonly = inject(ReadonlyInj, false)
const {
childrenList,
@ -43,7 +43,7 @@ const { isNew, state, removeLTARRef } = useSmartsheetRowStoreOrThrow()
watch(
[vModel, isForm],
(nextVal) => {
if (nextVal[0] || nextVal[1]) {
if ((nextVal[0] || nextVal[1]) && !isNew.value) {
loadChildrenList()
}
},
@ -59,6 +59,12 @@ const unlinkRow = async (row: Record<string, any>) => {
}
}
const unlinkIfNewRow = async (row: Record<string, any>) => {
if (isNew.value) {
removeLTARRef(row, column?.value as ColumnType)
}
}
const container = computed(() =>
isForm?.value
? h('div', {
@ -79,7 +85,7 @@ const expandedFormRow = ref()
<MdiReload v-if="!isForm" class="cursor-pointer text-gray-500" @click="loadChildrenList" />
<a-button v-if="editEnabled" type="primary" ghost class="!text-xs" size="small" @click="emit('attachRecord')">
<a-button v-if="!readonly" type="primary" ghost class="!text-xs" size="small" @click="emit('attachRecord')">
<div class="flex align-center gap-1">
<MdiLinkVariantRemove class="text-xs" type="primary" @click="unlinkRow(row)" />
Link to '{{ meta.title }}'
@ -101,18 +107,19 @@ const expandedFormRow = ref()
>
<div class="flex align-center">
<div class="flex-grow overflow-hidden min-w-0">
{{ row[relatedTablePrimaryValueProp]
}}<span class="text-gray-400 text-[11px] ml-1">(Primary key : {{ getRelatedTableRowId(row) }})</span>
{{ row[relatedTablePrimaryValueProp] }}
<span class="text-gray-400 text-[11px] ml-1">(Primary key : {{ getRelatedTableRowId(row) }})</span>
</div>
<div class="flex-1"></div>
<div v-if="editEnabled" class="flex gap-2">
<div v-if="!readonly" class="flex gap-2">
<MdiLinkVariantRemove
class="text-xs text-grey hover:(!text-red-500) cursor-pointer"
@click.stop="unlinkRow(row)"
/>
<MdiDeleteOutline
v-if="!readonly"
class="text-xs text-grey hover:(!text-red-500) cursor-pointer"
@click.stop="deleteRelatedRow(row)"
@click.stop="deleteRelatedRow(row, unlinkIfNewRow)"
/>
</div>
</div>
@ -128,7 +135,12 @@ const expandedFormRow = ref()
show-less-items
/>
</template>
<a-empty v-else class="my-10" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
<a-empty
v-else
:class="{ 'my-10': !isForm, 'my-1 !text-xs': isForm }"
:image="Empty.PRESENTED_IMAGE_SIMPLE"
:image-style="isForm ? { height: '20px' } : {}"
/>
</div>
<Suspense>

8
packages/nc-gui-v2/components/virtual-cell/components/ListItems.vue

@ -47,8 +47,12 @@ const linkRow = async (row: Record<string, any>) => {
vModel.value = false
}
watch(vModel, () => {
if (vModel.value) {
/** reload list on modal open */
watch(vModel, (nextVal, prevVal) => {
if (nextVal && !prevVal) {
/** reset query and limit */
childrenExcludedListPagination.query = ''
childrenExcludedListPagination.page = 1
loadChildrenExcludedList()
}
})

7
packages/nc-gui-v2/composables/useLTARStore.ts

@ -139,7 +139,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
}
}
const deleteRelatedRow = async (row: Record<string, any>) => {
const deleteRelatedRow = async (row: Record<string, any>, onSuccess?: (row: Record<string, any>) => void) => {
Modal.confirm({
title: 'Do you want to delete the record?',
type: 'warning',
@ -148,7 +148,12 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
try {
$api.dbTableRow.delete(NOCO, project.value.id as string, relatedTableMeta.value.id as string, id as string)
reloadData?.()
/** reload child list if not a new row */
if (!isNewRow?.value) {
await loadChildrenList()
}
onSuccess?.(row)
} catch (e: any) {
message.error(`Delete failed: ${await extractSdkResponseErrorMsg(e)}`)
}

7
packages/nc-gui-v2/composables/useSmartsheetRowStore.ts

@ -5,7 +5,7 @@ import type { Ref } from 'vue'
import type { Row } from './useViewData'
import { useInjectionState, useMetas, useNuxtApp, useProject, useVirtualCell } from '#imports'
import { NOCO } from '~/lib'
import { extractPkFromRow, extractSdkResponseErrorMsg } from '~/utils'
import { deepCompare, extractPkFromRow, extractSdkResponseErrorMsg } from '~/utils'
const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState((meta: Ref<TableType>, row: Ref<Row>) => {
const { $api } = useNuxtApp()
@ -23,6 +23,11 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column)))
if (isHm || isMm) {
state.value[column.title!] = state.value[column.title!] || []
if (state.value[column.title!]!.find((ln: Record<string, any>) => deepCompare(ln, value))) {
return message.info('This value is already in the list')
}
state.value[column.title!]!.push(value)
} else if (isBt) {
state.value[column.title!] = value

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

@ -148,6 +148,7 @@ export function useViewData(
})
await syncCount()
return insertedData
} catch (error: any) {
message.error(await extractSdkResponseErrorMsg(error))
}

2
packages/nc-gui-v2/context/index.ts

@ -19,7 +19,7 @@ export const IsGridInj: InjectionKey<boolean> = Symbol('is-grid-injection')
export const IsLockedInj: InjectionKey<boolean> = Symbol('is-locked-injection')
export const CellValueInj: InjectionKey<Ref<any>> = Symbol('cell-value-injection')
export const ActiveViewInj: InjectionKey<Ref<ViewType>> = Symbol('active-view-injection')
export const ReadonlyInj: InjectionKey<any> = Symbol('readonly-injection')
export const ReadonlyInj: InjectionKey<boolean> = Symbol('readonly-injection')
export const ReloadViewDataHookInj: InjectionKey<EventHook<void>> = Symbol('reload-view-data-injection')
export const FieldsInj: InjectionKey<Ref<any[]>> = Symbol('fields-injection')
export const ViewListInj: InjectionKey<Ref<ViewType[]>> = Symbol('view-list-injection')

Loading…
Cancel
Save