Browse Source

feat(nc-gui): drag & drop implemnetation for date time field

pull/7611/head
DarkPhoenix2704 10 months ago
parent
commit
678a482761
  1. 4
      packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue
  2. 272
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  3. 34
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  4. 16
      packages/nc-gui/composables/useCalendarViewStore.ts

4
packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue

@ -114,7 +114,7 @@ const dropEvent = (event: DragEvent) => {
...record, ...record,
row: { row: {
...record.row, ...record.row,
[fromCol.title!]: dayjs(newStartDate).format('YYYY-MM-DD'), [fromCol.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -133,7 +133,7 @@ const dropEvent = (event: DragEvent) => {
} else { } else {
endDate = newStartDate.clone() endDate = newStartDate.clone()
} }
newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD') newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
updateProperty.push(toCol.title!) updateProperty.push(toCol.title!)
} }

272
packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue

@ -1,5 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
import { UITypes } from 'nocodb-sdk'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { type Row, computed, ref } from '#imports' import { type Row, computed, ref } from '#imports'
@ -9,9 +8,12 @@ const fields = inject(FieldsInj, ref([]))
const container = ref() const container = ref()
const { isUIAllowed } = useRoles()
const displayField = computed(() => meta.value?.columns?.find((c) => c.pv && fields.value.includes(c)) ?? null) const displayField = computed(() => meta.value?.columns?.find((c) => c.pv && fields.value.includes(c)) ?? null)
const { selectedDate, selectedTime, calDataType, formattedData, calendarRange } = useCalendarViewStoreOrThrow() const { selectedDate, selectedTime, formattedData, calendarRange, formattedSideBarData, updateRowProperty } =
useCalendarViewStoreOrThrow()
const hours = computed(() => { const hours = computed(() => {
const hours: Array<dayjs.Dayjs> = [] const hours: Array<dayjs.Dayjs> = []
@ -40,9 +42,11 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
const scheduleStart = dayjs(selectedDate.value).startOf('day') const scheduleStart = dayjs(selectedDate.value).startOf('day')
const scheduleEnd = dayjs(selectedDate.value).endOf('day') const scheduleEnd = dayjs(selectedDate.value).endOf('day')
const overlaps = {} const overlaps: {
[key: string]: string[]
} = {}
const perRecordHeight = 40 const perRecordHeight = 80
if (!calendarRange.value) return [] if (!calendarRange.value) return []
@ -82,10 +86,9 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
overlaps[startMinutes] = [] overlaps[startMinutes] = []
} }
overlaps[startMinutes].push(id) overlaps[startMinutes].push(id)
startMinutes += 10 startMinutes += 15
} }
const finalTopInPixels = topInPixels + startHour
const finalTopInPixels = topInPixels + startHour + 1
const style: Partial<CSSStyleDeclaration> = { const style: Partial<CSSStyleDeclaration> = {
top: `${finalTopInPixels}px`, top: `${finalTopInPixels}px`,
@ -153,7 +156,7 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
range: range as any, range: range as any,
style: { style: {
top: `${finalTopInPixels}px`, top: `${finalTopInPixels}px`,
height: `${heightInPixels - 5}px`, height: `${heightInPixels + 5}px`,
}, },
id, id,
position: 'rounded', position: 'rounded',
@ -167,10 +170,10 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
let maxOverlaps = 1 let maxOverlaps = 1
let overlapIndex = 0 let overlapIndex = 0
for (const minutes in overlaps) { for (const minutes in overlaps) {
if (overlaps[minutes].includes(record.rowMeta.id)) { if (overlaps[minutes].includes(record.rowMeta.id!)) {
maxOverlaps = Math.max(maxOverlaps, overlaps[minutes].length) maxOverlaps = Math.max(maxOverlaps, overlaps[minutes].length)
overlapIndex = Math.max(overlaps[minutes].indexOf(record.rowMeta.id), overlapIndex) overlapIndex = Math.max(overlaps[minutes].indexOf(record.rowMeta.id!), overlapIndex)
} }
} }
@ -178,8 +181,6 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps
const leftPerRecord = (widthPerRecord + spacing) * overlapIndex const leftPerRecord = (widthPerRecord + spacing) * overlapIndex
console.log('leftPerRecord', leftPerRecord, 'widthPerRecord', widthPerRecord)
record.rowMeta.style = { record.rowMeta.style = {
...record.rowMeta.style, ...record.rowMeta.style,
left: `${leftPerRecord}%`, left: `${leftPerRecord}%`,
@ -190,13 +191,227 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
return recordsByRange return recordsByRange
}) })
const dragRecord = ref<Row | null>(null)
const isDragging = ref(false)
const draggingId = ref<string | null>(null)
const dragElement = ref<HTMLElement | null>(null)
const dragTimeout = ref(null)
const onDrag = (event: MouseEvent) => {
if (!isUIAllowed('dataEdit')) return
if (!container.value || !dragRecord.value) return
const { top } = container.value.getBoundingClientRect()
const { scrollHeight } = container.value
console.log(scrollHeight)
const percentY = (event.clientY - top - window.scrollY) / scrollHeight
const fromCol = dragRecord.value.rowMeta.range?.fk_from_col
const toCol = dragRecord.value.rowMeta.range?.fk_to_col
if (!fromCol) return
console.log('percentY', percentY)
const hour = Math.floor(percentY * 24)
const newStartDate = dayjs(selectedDate.value).add(hour, 'hour')
if (!newStartDate) return
let endDate
const newRow = {
...dragRecord.value,
row: {
...dragRecord.value.row,
[fromCol.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
},
}
if (toCol) {
const fromDate = dragRecord.value.row[fromCol.title!] ? dayjs(dragRecord.value.row[fromCol.title!]) : null
const toDate = dragRecord.value.row[toCol.title!] ? dayjs(dragRecord.value.row[toCol.title!]) : null
if (fromDate && toDate) {
endDate = dayjs(newStartDate).add(toDate.diff(fromDate, 'hour'), 'hour')
} else if (fromDate && !toDate) {
endDate = dayjs(newStartDate).endOf('hour')
} else if (!fromDate && toDate) {
endDate = dayjs(newStartDate).endOf('hour')
} else {
endDate = newStartDate.clone()
}
newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
}
console.log('newStartDate', newStartDate.format('YYYY-MM-DD HH:mm:ssZ'))
console.log('endDate', endDate?.format('YYYY-MM-DD HH:mm:ssZ'))
formattedData.value = formattedData.value.map((r) => {
const pk = extractPkFromRow(r.row, meta.value!.columns!)
if (pk === extractPkFromRow(newRow.row, meta.value!.columns!)) {
return newRow
}
return r
})
}
const stopDrag = (event: MouseEvent) => {
event.preventDefault()
clearTimeout(dragTimeout.value)
if (!isUIAllowed('dataEdit')) return
if (!isDragging.value || !container.value || !dragRecord.value) return
const { top } = container.value.getBoundingClientRect()
const { scrollHeight } = container.value
const percentY = (event.clientY - top - window.scrollY) / scrollHeight
const fromCol = dragRecord.value.rowMeta.range?.fk_from_col
const toCol = dragRecord.value.rowMeta.range?.fk_to_col
const hour = Math.floor(percentY * 24)
console.log('hour', hour)
const newStartDate = dayjs(selectedDate.value).add(hour, 'hour')
if (!newStartDate || !fromCol) return
let endDate
const newRow = {
...dragRecord.value,
row: {
...dragRecord.value.row,
[fromCol.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
},
}
const updateProperty = [fromCol.title!]
if (toCol) {
const fromDate = dragRecord.value.row[fromCol.title!] ? dayjs(dragRecord.value.row[fromCol.title!]) : null
const toDate = dragRecord.value.row[toCol.title!] ? dayjs(dragRecord.value.row[toCol.title!]) : null
if (fromDate && toDate) {
endDate = dayjs(newStartDate).add(toDate.diff(fromDate, 'hour'), 'hour')
} else if (fromDate && !toDate) {
endDate = dayjs(newStartDate).endOf('hour')
} else if (!fromDate && toDate) {
endDate = dayjs(newStartDate).endOf('hour')
} else {
endDate = newStartDate.clone()
}
newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
updateProperty.push(toCol.title!)
}
if (!newRow) return
if (dragElement.value) {
formattedData.value = formattedData.value.map((r) => {
const pk = extractPkFromRow(r.row, meta.value!.columns!)
if (pk === extractPkFromRow(newRow.row, meta.value!.columns!)) {
return newRow
}
return r
})
} else {
formattedData.value = [...formattedData.value, newRow]
formattedSideBarData.value = formattedSideBarData.value.filter((r) => {
const pk = extractPkFromRow(r.row, meta.value!.columns!)
return pk !== extractPkFromRow(newRow.row, meta.value!.columns!)
})
}
const allRecords = document.querySelectorAll('.draggable-record')
allRecords.forEach((el) => {
el.style.visibility = ''
el.style.opacity = '100%'
})
if (dragElement.value) {
dragElement.value.style.boxShadow = 'none'
dragElement.value.classList.remove('hide')
isDragging.value = false
draggingId.value = null
dragElement.value = null
}
updateRowProperty(newRow, updateProperty, false)
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
}
const dragStart = (event: MouseEvent, record: Row) => {
if (!isUIAllowed('dataEdit')) return
let target = event.target as HTMLElement
let isMoved = false
const onMouseMove = () => {
isMoved = true
document.removeEventListener('mousemove', onMouseMove)
}
document.addEventListener('mousemove', onMouseMove)
dragTimeout.value = setTimeout(() => {
if (isMoved) {
while (!target.classList.contains('draggable-record')) {
target = target.parentElement as HTMLElement
}
const allRecords = document.querySelectorAll('.draggable-record')
allRecords.forEach((el) => {
if (!el.getAttribute('data-unique-id').includes(record.rowMeta.id!)) {
// el.style.visibility = 'hidden'
el.style.opacity = '30%'
}
})
dragRecord.value = record
isDragging.value = true
dragElement.value = target
draggingId.value = record.rowMeta.id!
dragRecord.value = record
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', stopDrag)
} else {
clearTimeout(dragTimeout.value)
document.removeEventListener('mousemove', onMouseMove)
}
}, 50)
const onMouseUp = () => {
clearTimeout(dragTimeout.value)
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
document.addEventListener('mouseup', onMouseUp)
}
</script> </script>
<template> <template>
<div <div
v-if="recordsAcrossAllRange.length" v-if="recordsAcrossAllRange.length"
ref="container" ref="container"
class="w-full relative h-[calc(100vh-10rem)] overflow-y-auto nc-scrollbar-md" class="w-full relative no-selection h-[calc(100vh-10rem)] overflow-y-auto nc-scrollbar-md"
> >
<div <div
v-for="(hour, index) in hours" v-for="(hour, index) in hours"
@ -230,21 +445,21 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
<NcMenu class="w-64"> <NcMenu class="w-64">
<NcMenuItem> Select date field to add </NcMenuItem> <NcMenuItem> Select date field to add </NcMenuItem>
<NcMenuItem <NcMenuItem
v-for="(range, index) in calendarRange" v-for="(range, calIndex) in calendarRange"
:key="index" :key="calIndex"
class="text-gray-800 font-semibold text-sm" class="text-gray-800 font-semibold text-sm"
@click=" @click="
() => { () => {
let record = { let record = {
row: { row: {
[range.fk_from_col.title]: hour.format('YYYY-MM-DD HH:mm:ssZ'), [range.fk_from_col!.title!]: hour.format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
if (range.fk_to_col) { if (range.fk_to_col) {
record = { record = {
row: { row: {
...record.row, ...record.row,
[range.fk_to_col.title]: hour.add(1, 'hour').format('YYYY-MM-DD HH:mm:ssZ'), [range.fk_to_col!.title!]: hour.add(1, 'hour').format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
} }
@ -273,7 +488,7 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
() => { () => {
let record = { let record = {
row: { row: {
[calendarRange[0].fk_from_col.title]: hour.format('YYYY-MM-DD HH:mm:ssZ'), [calendarRange[0].fk_from_col!.title!]: hour.format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -281,7 +496,7 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
record = { record = {
row: { row: {
...record.row, ...record.row,
[calendarRange[0].fk_to_col.title]: hour.add(1, 'hour').format('YYYY-MM-DD HH:mm:ssZ'), [calendarRange[0].fk_to_col!.title!]: hour.add(1, 'hour').format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
} }
@ -292,15 +507,15 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
<component :is="iconMap.plus" class="h-4 w-4" /> <component :is="iconMap.plus" class="h-4 w-4" />
</NcButton> </NcButton>
</div> </div>
<div class="absolute inset-0"> <div class="absolute inset-0 pointer-events-none">
<div class="relative !ml-[50px]"> <div class="relative !ml-[50px]">
<div <div
v-for="(record, rowIndex) in recordsAcrossAllRange" v-for="(record, rowIndex) in recordsAcrossAllRange"
:key="rowIndex" :key="rowIndex"
:draggable="UITypes.DateTime === calDataType" :data-unique-id="record.rowMeta.id"
:style="record.rowMeta.style" :style="record.rowMeta.style"
class="absolute" class="absolute draggable-record pointer-events-auto"
@dragstart="dragStart($event, record)" @mousedown="dragStart($event, record)"
@dragover.prevent @dragover.prevent
> >
<LazySmartsheetRow :row="record"> <LazySmartsheetRow :row="record">
@ -325,4 +540,13 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
</div> </div>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.no-selection {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
}
</style>

34
packages/nc-gui/components/smartsheet/calendar/MonthView.vue

@ -304,8 +304,8 @@ const onDrag = (event: MouseEvent) => {
const percentY = (event.clientY - top - window.scrollY) / height const percentY = (event.clientY - top - window.scrollY) / height
const percentX = (event.clientX - left - window.scrollX) / width const percentX = (event.clientX - left - window.scrollX) / width
const fromCol = dragRecord.value.rowMeta.range?.fk_from_col const fromCol = dragRecord.value!.rowMeta.range?.fk_from_col
const toCol = dragRecord.value.rowMeta.range?.fk_to_col const toCol = dragRecord.value!.rowMeta.range?.fk_to_col
const week = Math.floor(percentY * dates.value.length) const week = Math.floor(percentY * dates.value.length)
const day = Math.floor(percentX * 7) const day = Math.floor(percentX * 7)
@ -322,14 +322,14 @@ const onDrag = (event: MouseEvent) => {
const newRow = { const newRow = {
...dragRecord.value, ...dragRecord.value,
row: { row: {
...dragRecord.value.row, ...dragRecord.value!.row,
[fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD'), [fromCol!.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
if (toCol) { if (toCol) {
const fromDate = dragRecord.value.row[fromCol.title] ? dayjs(dragRecord.value.row[fromCol.title]) : null const fromDate = dragRecord.value!.row[fromCol!.title!] ? dayjs(dragRecord!.value!.row[fromCol!.title!]) : null
const toDate = dragRecord.value.row[toCol.title] ? dayjs(dragRecord.value.row[toCol.title]) : null const toDate = dragRecord.value!.row[toCol!.title!] ? dayjs(dragRecord.value!.row[toCol!.title!]) : null
if (fromDate && toDate) { if (fromDate && toDate) {
endDate = dayjs(newStartDate).add(toDate.diff(fromDate, 'day'), 'day') endDate = dayjs(newStartDate).add(toDate.diff(fromDate, 'day'), 'day')
@ -341,13 +341,13 @@ const onDrag = (event: MouseEvent) => {
endDate = newStartDate.clone() endDate = newStartDate.clone()
} }
newRow.row[toCol.title] = dayjs(endDate).format('YYYY-MM-DD') newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD')
} }
formattedData.value = formattedData.value.map((r) => { formattedData.value = formattedData.value.map((r) => {
const pk = extractPkFromRow(r.row, meta.value.columns) const pk = extractPkFromRow(r.row, meta.value!.columns!)
if (pk === extractPkFromRow(newRow.row, meta.value.columns)) { if (pk === extractPkFromRow(newRow.row, meta.value!.columns!)) {
return newRow return newRow
} }
return r return r
@ -390,7 +390,7 @@ const onResize = (event: MouseEvent) => {
...resizeRecord.value, ...resizeRecord.value,
row: { row: {
...resizeRecord.value.row, ...resizeRecord.value.row,
[toCol.title]: dayjs(newEndDate).format('YYYY-MM-DD'), [toCol.title]: dayjs(newEndDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -416,7 +416,7 @@ const onResize = (event: MouseEvent) => {
...resizeRecord.value, ...resizeRecord.value,
row: { row: {
...resizeRecord.value.row, ...resizeRecord.value.row,
[fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD'), [fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -482,7 +482,7 @@ const stopDrag = (event: MouseEvent) => {
...dragRecord.value, ...dragRecord.value,
row: { row: {
...dragRecord.value.row, ...dragRecord.value.row,
[fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD'), [fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -503,7 +503,7 @@ const stopDrag = (event: MouseEvent) => {
} }
dragRecord.value = null dragRecord.value = null
newRow.row[toCol.title] = dayjs(endDate).format('YYYY-MM-DD') newRow.row[toCol.title] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
updateProperty.push(toCol.title) updateProperty.push(toCol.title)
} }
@ -611,7 +611,7 @@ const dropEvent = (event: DragEvent) => {
...record, ...record,
row: { row: {
...record.row, ...record.row,
[fromCol.title!]: dayjs(newStartDate).format('YYYY-MM-DD'), [fromCol.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -630,7 +630,7 @@ const dropEvent = (event: DragEvent) => {
} else { } else {
endDate = newStartDate.clone() endDate = newStartDate.clone()
} }
newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD') newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
updateProperty.push(toCol.title!) updateProperty.push(toCol.title!)
} }
@ -746,7 +746,7 @@ onBeforeUnmount(() => {
() => { () => {
const record = { const record = {
row: { row: {
[range.fk_from_col.title]: dayjs(day).format('YYYY-MM-DD'), [range.fk_from_col.title]: dayjs(day).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
emit('new-record', record) emit('new-record', record)
@ -773,7 +773,7 @@ onBeforeUnmount(() => {
() => { () => {
const record = { const record = {
row: { row: {
[calendarRange[0].fk_from_col.title]: dayjs(day).format('YYYY-MM-DD'), [calendarRange[0].fk_from_col.title]: dayjs(day).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
emit('new-record', record) emit('new-record', record)

16
packages/nc-gui/composables/useCalendarViewStore.ts

@ -182,11 +182,11 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
} }
if (calDataType.value === UITypes.Date) { if (calDataType.value === UITypes.Date) {
fromDate = dayjs(fromDate).format('YYYY-MM-DD') fromDate = dayjs(fromDate).format('YYYY-MM-DDZ')
toDate = dayjs(toDate).format('YYYY-MM-DD') toDate = dayjs(toDate).format('YYYY-MM-DDZ')
} else if (calDataType.value === UITypes.DateTime) { } else if (calDataType.value === UITypes.DateTime) {
fromDate = dayjs(fromDate).format('YYYY-MM-DD HH:mm:ss') fromDate = dayjs(fromDate).format('YYYY-MM-DD HH:mm:ssZ')
toDate = dayjs(toDate).format('YYYY-MM-DD HH:mm:ss') toDate = dayjs(toDate).format('YYYY-MM-DD HH:mm:ssZ')
} }
calendarRange.value.forEach((range) => { calendarRange.value.forEach((range) => {
@ -353,11 +353,11 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
} }
if (calDataType.value === UITypes.Date) { if (calDataType.value === UITypes.Date) {
fromDate = dayjs(fromDate).format('YYYY-MM-DD') fromDate = dayjs(fromDate).format('YYYY-MM-DDZ')
toDate = dayjs(toDate).format('YYYY-MM-DD') toDate = dayjs(toDate).format('YYYY-MM-DDZ')
} else if (calDataType.value === UITypes.DateTime) { } else if (calDataType.value === UITypes.DateTime) {
fromDate = dayjs(fromDate).format('YYYY-MM-DD HH:mm:ss') fromDate = dayjs(fromDate).format('YYYY-MM-DD HH:mm:ssZ')
toDate = dayjs(toDate).format('YYYY-MM-DD HH:mm:ss') toDate = dayjs(toDate).format('YYYY-MM-DD HH:mm:ssZ')
} }
calendarRange.value.forEach((range) => { calendarRange.value.forEach((range) => {

Loading…
Cancel
Save