|
|
|
@ -7,8 +7,8 @@ import {
|
|
|
|
|
provide, |
|
|
|
|
useGridViewColumnWidth, |
|
|
|
|
useProvideColumnCreateStore, |
|
|
|
|
useSmartsheetStoreOrThrow, |
|
|
|
|
useViewData, |
|
|
|
|
useSmartsheetStoreOrThrow |
|
|
|
|
} from '#imports' |
|
|
|
|
import { |
|
|
|
|
ActiveViewInj, |
|
|
|
@ -34,15 +34,15 @@ const isLocked = inject(IsLockedInj, false)
|
|
|
|
|
// todo: get from parent ( inject or use prop ) |
|
|
|
|
const isPublicView = false |
|
|
|
|
|
|
|
|
|
const selected = reactive<{ row?: number | null; col?: number | null }>({}) |
|
|
|
|
const editEnabled = ref(false) |
|
|
|
|
const selected = reactive<{ row: number | null; col: number | null }>({ row: null, col: null }) |
|
|
|
|
let editEnabled = $ref(false) |
|
|
|
|
const { sqlUi } = useProject() |
|
|
|
|
const { xWhere } = useSmartsheetStoreOrThrow() |
|
|
|
|
const addColumnDropdown = ref(false) |
|
|
|
|
const contextMenu = ref(false) |
|
|
|
|
const contextMenuTarget = ref(false) |
|
|
|
|
|
|
|
|
|
const visibleColLength = computed(() => { |
|
|
|
|
const visibleColLength = $computed(() => { |
|
|
|
|
const cols = fields.value |
|
|
|
|
return cols.filter((col) => !isVirtualCol(col)).length |
|
|
|
|
}) |
|
|
|
@ -58,7 +58,7 @@ const {
|
|
|
|
|
deleteRow, |
|
|
|
|
deleteSelectedRows, |
|
|
|
|
} = useViewData(meta, view as any, xWhere) |
|
|
|
|
const { loadGridViewColumns, updateWidth, resizingColWidth, resizingCol } = useGridViewColumnWidth(view) |
|
|
|
|
const { loadGridViewColumns, updateWidth, resizingColWidth, resizingCol } = useGridViewColumnWidth(view as any) |
|
|
|
|
onMounted(loadGridViewColumns) |
|
|
|
|
|
|
|
|
|
provide(IsFormInj, false) |
|
|
|
@ -79,7 +79,7 @@ const selectCell = (row: number, col: number) => {
|
|
|
|
|
|
|
|
|
|
onKeyStroke(['Enter'], (e) => { |
|
|
|
|
if (selected.row !== null && selected.col !== null) { |
|
|
|
|
editEnabled.value = true |
|
|
|
|
editEnabled = true |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
@ -112,11 +112,189 @@ if (meta) useProvideColumnCreateStore(meta)
|
|
|
|
|
|
|
|
|
|
// reset context menu target on hide |
|
|
|
|
watch(contextMenu, () => { |
|
|
|
|
if(!contextMenu.value) { |
|
|
|
|
if (!contextMenu.value) { |
|
|
|
|
contextMenuTarget.value = false |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
/** handle keypress events */ |
|
|
|
|
onKeyStroke(['Tab', 'Shift', ''], (e: KeyboardEvent) => { |
|
|
|
|
if (selected.row !== null && selected.col !== null) { |
|
|
|
|
/** on tab key press navigate through cells */ |
|
|
|
|
if (e.key === 'Tab') { |
|
|
|
|
e.preventDefault() |
|
|
|
|
if (e.shiftKey) { |
|
|
|
|
if (selected.col > 0) { |
|
|
|
|
selected.col-- |
|
|
|
|
} else if (selected.row > 0) { |
|
|
|
|
selected.row-- |
|
|
|
|
selected.col = visibleColLength - 1 |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (selected.col < visibleColLength - 1) { |
|
|
|
|
selected.col++ |
|
|
|
|
} else if (selected.row < data.value.length - 1) { |
|
|
|
|
selected.row++ |
|
|
|
|
selected.col = 0 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
/** on shift + tab key press navigate through cells in reverse order */ |
|
|
|
|
// onKeyStroke(, (e) => { |
|
|
|
|
// if (selected.row !== null && selected.col !== null) { |
|
|
|
|
// e.preventDefault() |
|
|
|
|
// if (selected.col > 0) { |
|
|
|
|
// selected.col-- |
|
|
|
|
// } else if (selected.row > 0) { |
|
|
|
|
// selected.row-- |
|
|
|
|
// selected.col = visibleColLength - 1 |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
// }) |
|
|
|
|
// /** on delete key press clear the cell */ |
|
|
|
|
// onKeyStroke(['Tab', 'Shift'], (e) => { |
|
|
|
|
// if (selected.row !== null && selected.col !== null) { |
|
|
|
|
// e.preventDefault() |
|
|
|
|
// // check and clear cell |
|
|
|
|
// } |
|
|
|
|
// }) |
|
|
|
|
// /** on enter key press make cell editable */ |
|
|
|
|
// onKeyStroke('Enter', (e) => { |
|
|
|
|
// if (selected.row !== null && selected.col !== null) { |
|
|
|
|
// e.preventDefault() |
|
|
|
|
// editEnabled = true |
|
|
|
|
// } |
|
|
|
|
// }) |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
|
|
async onKeyDown(e) { |
|
|
|
|
if (selected.col === null || selected.row === null || isLocked) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch (e.keyCode) { |
|
|
|
|
// tab |
|
|
|
|
case 9: |
|
|
|
|
e.preventDefault(); |
|
|
|
|
this.editEnabled = { |
|
|
|
|
col: null, |
|
|
|
|
row: null, |
|
|
|
|
}; |
|
|
|
|
if (e.shiftKey) { |
|
|
|
|
if (this.selected.col > 0) { |
|
|
|
|
this.selected.col--; |
|
|
|
|
} else if (this.selected.row > 0) { |
|
|
|
|
this.selected.row--; |
|
|
|
|
this.selected.col = this.colLength - 1; |
|
|
|
|
} |
|
|
|
|
} else if (this.selected.col < this.colLength - 1) { |
|
|
|
|
this.selected.col++; |
|
|
|
|
} else if (this.selected.row < this.rowLength - 1) { |
|
|
|
|
this.selected.row++; |
|
|
|
|
this.selected.col = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
// delete |
|
|
|
|
case 46: |
|
|
|
|
{ |
|
|
|
|
if (this.editEnabled.col != null && this.editEnabled.row != null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const rowObj = this.data[this.selected.row].row; |
|
|
|
|
const columnObj = this.availableColumns[this.selected.col]; |
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
// this.isRequired(columnObj, rowObj, true) || |
|
|
|
|
columnObj.virtual |
|
|
|
|
) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.$set(rowObj, columnObj.title, null); |
|
|
|
|
// update/save cell value |
|
|
|
|
this.onCellValueChange(this.selected.col, this.selected.row, columnObj, true); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
// left |
|
|
|
|
case 37: |
|
|
|
|
if (this.rightToLeftLanguages.includes(this.$store.state.settings.language)) { |
|
|
|
|
if (this.selected.col < this.colLength - 1) { |
|
|
|
|
this.selected.col++; |
|
|
|
|
} |
|
|
|
|
} else if (this.selected.col > 0) { |
|
|
|
|
this.selected.col--; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
// right |
|
|
|
|
case 39: |
|
|
|
|
if (this.rightToLeftLanguages.includes(this.$store.state.settings.language)) { |
|
|
|
|
if (this.selected.col > 0) { |
|
|
|
|
this.selected.col--; |
|
|
|
|
} |
|
|
|
|
} else if (this.selected.col < this.colLength - 1) { |
|
|
|
|
this.selected.col++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
// up |
|
|
|
|
case 38: |
|
|
|
|
if (this.selected.row > 0) { |
|
|
|
|
this.selected.row--; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
// down |
|
|
|
|
case 40: |
|
|
|
|
if (this.selected.row < this.rowLength - 1) { |
|
|
|
|
this.selected.row++; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
// enter |
|
|
|
|
case 13: |
|
|
|
|
this.makeEditable(this.selected.col, this.selected.row); |
|
|
|
|
break; |
|
|
|
|
default: { |
|
|
|
|
if (this.editEnabled.col != null && this.editEnabled.row != null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const rowObj = this.data[this.selected.row].row; |
|
|
|
|
const columnObj = this.availableColumns[this.selected.col]; |
|
|
|
|
|
|
|
|
|
if (e.metaKey || e.ctrlKey) { |
|
|
|
|
switch (e.keyCode) { |
|
|
|
|
// copy - ctrl/cmd +c |
|
|
|
|
case 67: |
|
|
|
|
copyTextToClipboard(rowObj[columnObj.title] || ''); |
|
|
|
|
break; |
|
|
|
|
// // paste ctrl/cmd + v |
|
|
|
|
// case 86: { |
|
|
|
|
// const text = await navigator.clipboard.readText() |
|
|
|
|
// this.$set(rowObj, columnObj.title, text) |
|
|
|
|
// } |
|
|
|
|
// break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (e.ctrlKey || e.altKey || e.metaKey) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (e.key && e.key.length === 1) { |
|
|
|
|
if (!this.isPkAvail && !this.data[this.selected.row].rowMeta.new) { |
|
|
|
|
return this.$toast.info("Update not allowed for table which doesn't have primary Key").goAway(3000); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.$set(this.data[this.selected.row].row, this.availableColumns[this.selected.col].title, ''); |
|
|
|
|
this.editEnabled = { ...this.selected }; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
*/ |
|
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
<template> |
|
|
|
@ -125,92 +303,93 @@ watch(contextMenu, () => {
|
|
|
|
|
<div class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-primary"> |
|
|
|
|
<table class="xc-row-table nc-grid backgroundColorDefault" @contextmenu.prevent="contextMenu = true"> |
|
|
|
|
<thead> |
|
|
|
|
<tr> |
|
|
|
|
<th>#</th> |
|
|
|
|
<th |
|
|
|
|
v-for="col in fields" |
|
|
|
|
:key="col.title" |
|
|
|
|
v-xc-ver-resize |
|
|
|
|
:data-col="col.id" |
|
|
|
|
@xcresize="onresize(col.id, $event)" |
|
|
|
|
@xcresizing="onXcResizing(col.title, $event)" |
|
|
|
|
@xcresized="resizingCol = null" |
|
|
|
|
> |
|
|
|
|
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" /> |
|
|
|
|
<SmartsheetHeaderCell v-else :column="col" /> |
|
|
|
|
</th> |
|
|
|
|
<!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" --> |
|
|
|
|
<th v-t="['c:column:add']" @click="addColumnDropdown = true"> |
|
|
|
|
<a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']"> |
|
|
|
|
<div class="h-full w-full flex align-center justify-center"> |
|
|
|
|
<MdiPlusIcon class="text-sm" /> |
|
|
|
|
</div> |
|
|
|
|
<template #overlay> |
|
|
|
|
<SmartsheetColumnEditOrAdd @click.stop @cancel="addColumnDropdown = false" /> |
|
|
|
|
</template> |
|
|
|
|
</a-dropdown> |
|
|
|
|
</th> |
|
|
|
|
</tr> |
|
|
|
|
<tr> |
|
|
|
|
<th>#</th> |
|
|
|
|
<th |
|
|
|
|
v-for="col in fields" |
|
|
|
|
:key="col.title" |
|
|
|
|
v-xc-ver-resize |
|
|
|
|
:data-col="col.id" |
|
|
|
|
@xcresize="onresize(col.id, $event)" |
|
|
|
|
@xcresizing="onXcResizing(col.title, $event)" |
|
|
|
|
@xcresized="resizingCol = null" |
|
|
|
|
> |
|
|
|
|
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" /> |
|
|
|
|
<SmartsheetHeaderCell v-else :column="col" /> |
|
|
|
|
</th> |
|
|
|
|
<!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" --> |
|
|
|
|
<th v-t="['c:column:add']" @click="addColumnDropdown = true"> |
|
|
|
|
<a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']"> |
|
|
|
|
<div class="h-full w-full flex align-center justify-center"> |
|
|
|
|
<MdiPlusIcon class="text-sm" /> |
|
|
|
|
</div> |
|
|
|
|
<template #overlay> |
|
|
|
|
<SmartsheetColumnEditOrAdd @click.stop @cancel="addColumnDropdown = false" /> |
|
|
|
|
</template> |
|
|
|
|
</a-dropdown> |
|
|
|
|
</th> |
|
|
|
|
</tr> |
|
|
|
|
</thead> |
|
|
|
|
<tbody> |
|
|
|
|
<tr v-for="(row, rowIndex) in data" :key="rowIndex" class="nc-grid-row group"> |
|
|
|
|
<td key="row-index" class="caption nc-grid-cell"> |
|
|
|
|
<div class="align-center flex w-[80px]"> |
|
|
|
|
<div class="group-hover:hidden" :class="{ hidden: row.rowMeta.selected }">{{ rowIndex + 1 }}</div> |
|
|
|
|
<div |
|
|
|
|
:class="{ hidden: !row.rowMeta.selected, flex: row.rowMeta.selected }" |
|
|
|
|
class="group-hover:flex w-full align-center" |
|
|
|
|
> |
|
|
|
|
<a-checkbox v-model:checked="row.rowMeta.selected" /> |
|
|
|
|
<span class="flex-1" /> |
|
|
|
|
<MdiArrowExpandIcon class="text-sm text-pink hidden group-hover:inline-block" /> |
|
|
|
|
</div> |
|
|
|
|
<tr v-for="(row, rowIndex) in data" :key="rowIndex" class="nc-grid-row group"> |
|
|
|
|
<td key="row-index" class="caption nc-grid-cell"> |
|
|
|
|
<div class="align-center flex w-[80px]"> |
|
|
|
|
<div class="group-hover:hidden" :class="{ hidden: row.rowMeta.selected }">{{ rowIndex + 1 }}</div> |
|
|
|
|
<div |
|
|
|
|
:class="{ hidden: !row.rowMeta.selected, flex: row.rowMeta.selected }" |
|
|
|
|
class="group-hover:flex w-full align-center" |
|
|
|
|
> |
|
|
|
|
<a-checkbox v-model:checked="row.rowMeta.selected" /> |
|
|
|
|
<span class="flex-1" /> |
|
|
|
|
<MdiArrowExpandIcon class="text-sm text-pink hidden group-hover:inline-block" /> |
|
|
|
|
</div> |
|
|
|
|
</td> |
|
|
|
|
<td |
|
|
|
|
v-for="(columnObj, colIndex) in fields" |
|
|
|
|
:key="rowIndex + columnObj.title" |
|
|
|
|
class="cell pointer nc-grid-cell" |
|
|
|
|
:class="{ |
|
|
|
|
</div> |
|
|
|
|
</td> |
|
|
|
|
<td |
|
|
|
|
v-for="(columnObj, colIndex) in fields" |
|
|
|
|
:key="rowIndex + columnObj.title" |
|
|
|
|
class="cell pointer nc-grid-cell" |
|
|
|
|
:class="{ |
|
|
|
|
active: !isPublicView && selected.col === colIndex && selected.row === rowIndex, |
|
|
|
|
}" |
|
|
|
|
:data-col="columnObj.id" |
|
|
|
|
@click="selectCell(rowIndex, colIndex)" |
|
|
|
|
@dblclick="editEnabled = true" |
|
|
|
|
@contextmenu="contextMenuTarget = { row: rowIndex, col: colIndex }" |
|
|
|
|
> |
|
|
|
|
<SmartsheetVirtualCell v-if="isVirtualCol(columnObj)" v-model="row.row[columnObj.title]" :column="columnObj" /> |
|
|
|
|
|
|
|
|
|
<SmartsheetCell |
|
|
|
|
v-else |
|
|
|
|
v-model="row.row[columnObj.title]" |
|
|
|
|
:column="columnObj" |
|
|
|
|
:edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex" |
|
|
|
|
@update:model-value="updateOrSaveRow(row, columnObj.title)" |
|
|
|
|
/> |
|
|
|
|
</td> |
|
|
|
|
</tr> |
|
|
|
|
|
|
|
|
|
<tr v-if="!isLocked"> |
|
|
|
|
<td |
|
|
|
|
v-t="['c:row:add:grid-bottom']" |
|
|
|
|
:colspan="visibleColLength + 1" |
|
|
|
|
class="text-left pointer nc-grid-add-new-cell" |
|
|
|
|
@click="addEmptyRow()" |
|
|
|
|
> |
|
|
|
|
<a-tooltip top left> |
|
|
|
|
<div class="w-min flex align-center"> |
|
|
|
|
<MdiPlusIcon class="text-pint-500 text-xs" /> |
|
|
|
|
<span class="ml-1 caption grey--text"> |
|
|
|
|
:data-col="columnObj.id" |
|
|
|
|
@click="selectCell(rowIndex, colIndex)" |
|
|
|
|
@dblclick="editEnabled = true" |
|
|
|
|
@contextmenu="contextMenuTarget = { row: rowIndex, col: colIndex }" |
|
|
|
|
> |
|
|
|
|
<SmartsheetVirtualCell v-if="isVirtualCol(columnObj)" v-model="row.row[columnObj.title]" |
|
|
|
|
:column="columnObj" /> |
|
|
|
|
|
|
|
|
|
<SmartsheetCell |
|
|
|
|
v-else |
|
|
|
|
v-model="row.row[columnObj.title]" |
|
|
|
|
:column="columnObj" |
|
|
|
|
:edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex" |
|
|
|
|
@update:model-value="updateOrSaveRow(row, columnObj.title)" |
|
|
|
|
/> |
|
|
|
|
</td> |
|
|
|
|
</tr> |
|
|
|
|
|
|
|
|
|
<tr v-if="!isLocked"> |
|
|
|
|
<td |
|
|
|
|
v-t="['c:row:add:grid-bottom']" |
|
|
|
|
:colspan="visibleColLength + 1" |
|
|
|
|
class="text-left pointer nc-grid-add-new-cell" |
|
|
|
|
@click="addEmptyRow()" |
|
|
|
|
> |
|
|
|
|
<a-tooltip top left> |
|
|
|
|
<div class="w-min flex align-center"> |
|
|
|
|
<MdiPlusIcon class="text-pint-500 text-xs" /> |
|
|
|
|
<span class="ml-1 caption grey--text"> |
|
|
|
|
{{ $t('activity.addRow') }} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
<template #title> |
|
|
|
|
<span class="caption"> Add new row</span> |
|
|
|
|
</template> |
|
|
|
|
</a-tooltip> |
|
|
|
|
</td> |
|
|
|
|
</tr> |
|
|
|
|
</div> |
|
|
|
|
<template #title> |
|
|
|
|
<span class="caption"> Add new row</span> |
|
|
|
|
</template> |
|
|
|
|
</a-tooltip> |
|
|
|
|
</td> |
|
|
|
|
</tr> |
|
|
|
|
</tbody> |
|
|
|
|
</table> |
|
|
|
|
</div> |
|
|
|
@ -219,7 +398,9 @@ watch(contextMenu, () => {
|
|
|
|
|
<div v-if="contextMenuTarget" class="nc-menu-item" @click="deleteRow(contextMenuTarget.row)">Delete row</div> |
|
|
|
|
<div class="nc-menu-item" @click="deleteSelectedRows">Delete all selected rows</div> |
|
|
|
|
<div v-if="contextMenuTarget" class="nc-menu-item">Clear cell</div> |
|
|
|
|
<div v-if="contextMenuTarget" class="nc-menu-item" @click="addEmptyRow(contextMenuTarget.row + 1)">Insert new row</div> |
|
|
|
|
<div v-if="contextMenuTarget" class="nc-menu-item" @click="addEmptyRow(contextMenuTarget.row + 1)">Insert new |
|
|
|
|
row |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
</a-dropdown> |
|
|
|
@ -283,7 +464,8 @@ watch(contextMenu, () => {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
td.active::before { |
|
|
|
|
background: #0040bc /*var(--v-primary-base)*/; |
|
|
|
|
background: #0040bc /*var(--v-primary-base)*/ |
|
|
|
|
; |
|
|
|
|
opacity: 0.1; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|