Browse Source

feat(gui-v2): enter key navigation

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/2992/head
Pranav C 2 years ago
parent
commit
92cdc64119
  1. 23
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  2. 127
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  3. 188
      packages/nc-gui-v2/components/smartsheet/VirtualCell.vue
  4. 5
      packages/nc-gui-v2/lib/enums.ts

23
packages/nc-gui-v2/components/smartsheet/Cell.vue

@ -4,6 +4,7 @@ import type { ColumnType } from 'nocodb-sdk'
import { provide, toRef } from 'vue' import { provide, toRef } from 'vue'
import { computed, useColumn, useDebounceFn, useVModel } from '#imports' import { computed, useColumn, useDebounceFn, useVModel } from '#imports'
import { ColumnInj, EditModeInj } from '~/context' import { ColumnInj, EditModeInj } from '~/context'
import { NavigateDir } from '~/lib'
interface Props { interface Props {
column: ColumnType column: ColumnType
@ -17,7 +18,7 @@ interface Emits {
const { column, ...props } = defineProps<Props>() const { column, ...props } = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'save']) const emit = defineEmits(['update:modelValue', 'save', 'navigate'])
provide(ColumnInj, column) provide(ColumnInj, column)
@ -25,6 +26,7 @@ provide(EditModeInj, toRef(props, 'editEnabled'))
let changed = $ref(false) let changed = $ref(false)
const syncValue = useDebounceFn(function () { const syncValue = useDebounceFn(function () {
changed = false
emit('save') emit('save')
}, 1000) }, 1000)
@ -59,6 +61,7 @@ const vModel = computed({
syncValue() syncValue()
} else if (!isManualSaved) { } else if (!isManualSaved) {
emit('save') emit('save')
changed = true
} }
} }
}, },
@ -87,10 +90,26 @@ const {
isPercent, isPercent,
isPhoneNumber, isPhoneNumber,
} = useColumn(column) } = useColumn(column)
const syncAndNavigate = (dir: NavigateDir) => {
if (changed) {
emit('save')
changed = false
}
emit('navigate', dir)
}
</script> </script>
<template> <template>
<div class="nc-cell" @keydown.stop.left @keydown.stop.right @keydown.stop.up @keydown.stop.down> <div
class="nc-cell"
@keydown.stop.left
@keydown.stop.right
@keydown.stop.up
@keydown.stop.down
@keydown.stop.enter.exact="syncAndNavigate(NavigateDir.NEXT)"
@keydown.stop.shift.enter.exact="syncAndNavigate(NavigateDir.PREV)"
>
<CellTextArea v-if="isTextArea" v-model="vModel" /> <CellTextArea v-if="isTextArea" v-model="vModel" />
<CellCheckbox v-else-if="isBoolean" v-model="vModel" /> <CellCheckbox v-else-if="isBoolean" v-model="vModel" />
<CellAttachment v-else-if="isAttachment" v-model="vModel" /> <CellAttachment v-else-if="isAttachment" v-model="vModel" />

127
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isVirtualCol } from 'nocodb-sdk' import { isVirtualCol } from 'nocodb-sdk'
import { import {
Row,
inject, inject,
onKeyStroke, onKeyStroke,
onMounted, onMounted,
@ -22,8 +21,9 @@ import {
PaginationDataInj, PaginationDataInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
} from '~/context' } from '~/context'
import MdiPlusIcon from '~icons/mdi/plus' import { NavigateDir } from '~/lib'
import MdiArrowExpandIcon from '~icons/mdi/arrow-expand' import MdiArrowExpandIcon from '~icons/mdi/arrow-expand'
import MdiPlusIcon from '~icons/mdi/plus'
const meta = inject(MetaInj) const meta = inject(MetaInj)
const view = inject(ActiveViewInj) const view = inject(ActiveViewInj)
@ -128,57 +128,78 @@ const clearCell = async (ctx: { row: number; col: number }) => {
/** handle keypress events */ /** handle keypress events */
onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'], async (e: KeyboardEvent) => { onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'], async (e: KeyboardEvent) => {
if (selected.row !== null && selected.col !== null) { if (selected.row === null || selected.col === null) return
/** on tab key press navigate through cells */ /** on tab key press navigate through cells */
switch (e.key) { switch (e.key) {
case 'Tab': case 'Tab':
e.preventDefault() e.preventDefault()
if (e.shiftKey) { if (e.shiftKey) {
if (selected.col > 0) { if (selected.col > 0) {
selected.col-- selected.col--
} else if (selected.row > 0) { } else if (selected.row > 0) {
selected.row-- selected.row--
selected.col = visibleColLength - 1 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
}
} }
break } else {
/** on enter key press make cell editable */ if (selected.col < visibleColLength - 1) {
case 'Enter': selected.col++
e.preventDefault() } else if (selected.row < data.value.length - 1) {
editEnabled = true selected.row++
break selected.col = 0
/** on delete key press clear cell */ }
case 'Delete': }
e.preventDefault() break
await clearCell(selected as { row: number; col: number }) /** on enter key press make cell editable */
break case 'Enter':
/** on arrow key press navigate through cells */ e.preventDefault()
case 'ArrowRight': editEnabled = true
e.preventDefault() break
if (selected.col < visibleColLength - 1) selected.col++ /** on delete key press clear cell */
break case 'Delete':
case 'ArrowLeft': e.preventDefault()
e.preventDefault() await clearCell(selected as { row: number; col: number })
if (selected.col > 0) selected.col-- break
break /** on arrow key press navigate through cells */
case 'ArrowUp': case 'ArrowRight':
e.preventDefault() e.preventDefault()
if (selected.row > 0) selected.row-- if (selected.col < visibleColLength - 1) selected.col++
break break
case 'ArrowDown': case 'ArrowLeft':
e.preventDefault() e.preventDefault()
if (selected.row < data.value.length - 1) selected.row++ if (selected.col > 0) selected.col--
break break
} case 'ArrowUp':
e.preventDefault()
if (selected.row > 0) selected.row--
break
case 'ArrowDown':
e.preventDefault()
if (selected.row < data.value.length - 1) selected.row++
break
} }
}) })
const onNavigate = (dir: NavigateDir) => {
if (selected.row === null || selected.col === null) return
switch (dir) {
case NavigateDir.NEXT:
if (selected.col < visibleColLength - 1) {
selected.col++
} else if (selected.row < data.value.length - 1) {
selected.row++
selected.col = 0
}
break
case NavigateDir.PREV:
if (selected.col > 0) {
selected.col--
} else if (selected.row > 0) {
selected.row--
selected.col = visibleColLength - 1
}
break
}
}
</script> </script>
<template> <template>
@ -254,7 +275,9 @@ onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLe
> >
<div class="w-full h-full"> <div class="w-full h-full">
<SmartsheetVirtualCell v-if="isVirtualCol(columnObj)" v-model="row.row[columnObj.title]" <SmartsheetVirtualCell v-if="isVirtualCol(columnObj)" v-model="row.row[columnObj.title]"
:column="columnObj" /> :column="columnObj"
@navigate="onNavigate"
/>
<SmartsheetCell <SmartsheetCell
v-else v-else
@ -262,6 +285,7 @@ onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLe
:column="columnObj" :column="columnObj"
:edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex" :edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex"
@save="updateOrSaveRow(row, columnObj.title)" @save="updateOrSaveRow(row, columnObj.title)"
@navigate="onNavigate"
/> />
</div> </div>
</td> </td>
@ -326,7 +350,6 @@ onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLe
@apply flex align-center h-auto; @apply flex align-center h-auto;
padding: 0 5px; padding: 0 5px;
} }
} }
table, table,

188
packages/nc-gui-v2/components/smartsheet/VirtualCell.vue

@ -2,6 +2,8 @@
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { provide, useVirtualCell } from '#imports' import { provide, useVirtualCell } from '#imports'
import { ColumnInj } from '~/context' import { ColumnInj } from '~/context'
import { NavigateDir } from '~/lib'
interface Props { interface Props {
column: ColumnType column: ColumnType
@ -10,7 +12,7 @@ interface Props {
const { column, modelValue: value } = defineProps<Props>() const { column, modelValue: value } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue', 'navigate'])
provide(ColumnInj, column) provide(ColumnInj, column)
provide('value', value) provide('value', value)
@ -19,196 +21,18 @@ const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualC
</script> </script>
<template> <template>
<div class="nc-virtual-cell"> <div class="nc-virtual-cell"
@keydown.stop.enter.exact="emit('navigate',NavigateDir.NEXT)"
@keydown.stop.shift.enter.exact="emit('navigate',NavigateDir.PREV)">
<VirtualCellHasMany v-if="isHm" /> <VirtualCellHasMany v-if="isHm" />
<VirtualCellManyToMany v-else-if="isMm" /> <VirtualCellManyToMany v-else-if="isMm" />
<VirtualCellBelongsTo v-else-if="isBt" /> <VirtualCellBelongsTo v-else-if="isBt" />
<VirtualCellRollup v-else-if="isRollup" /> <VirtualCellRollup v-else-if="isRollup" />
<VirtualCellFormula v-else-if="isFormula" /> <VirtualCellFormula v-else-if="isFormula" />
<VirtualCellCount v-else-if="isCount" /> <VirtualCellCount v-else-if="isCount" />
<!-- <v-lazy> -->
<!-- <has-many-cell
v-if="hm"
ref="cell"
:row="row"
:value="row[column.title]"
:meta="meta"
:hm="hm"
:nodes="nodes"
:active="active"
:sql-ui="sqlUi"
:is-new="isNew"
:is-form="isForm"
:breadcrumbs="breadcrumbs"
:is-locked="isLocked"
:required="required"
:is-public="isPublic"
:metas="metas"
:column="column"
:password="password"
v-on="$listeners"
/>
<many-to-many-cell
v-else-if="mm"
ref="cell"
:is-public="isPublic"
:row="row"
:value="row[column.title]"
:meta="meta"
:nodes="nodes"
:sql-ui="sqlUi"
:active="active"
:is-new="isNew"
:api="api"
:is-form="isForm"
:breadcrumbs="breadcrumbs"
:is-locked="isLocked"
:required="required"
:column="column"
:metas="metas"
:password="password"
v-on="$listeners"
/>
<belongs-to-cell
v-else-if="bt"
ref="cell"
:is-public="isPublic"
:disabled-columns="disabledColumns"
:active="active"
:row="row"
:value="row[column.title]"
:meta="meta"
:nodes="nodes"
:api="api"
:sql-ui="sqlUi"
:is-new="isNew"
:is-form="isForm"
:breadcrumbs="breadcrumbs"
:is-locked="isLocked"
:metas="metas"
:column="column"
:password="password"
v-on="$listeners"
/>
<lookup-cell
v-else-if="lookup"
:disabled-columns="disabledColumns"
:active="active"
:row="row"
:value="row[column.title]"
:meta="meta"
:metas="metas"
:nodes="nodes"
:api="api"
:sql-ui="sqlUi"
:is-new="isNew"
:is-form="isForm"
:column="column"
:is-locked="isLocked"
v-on="$listeners "
/>
<formula-cell
v-else-if="formula"
:row="row"
:column="column"
:client="nodes.dbConnection.client"
/>
<rollup-cell
v-else-if="rollup"
:row="row"
:column="column"
/>
</v-lazy>
<span v-if="hint" class="nc-hint">{{ hint }}</span>
<div v-if="isLocked" class="nc-locked-overlay" /> -->
</div> </div>
</template> </template>
<!-- <script>
import { UITypes } from "nocodb-sdk";
import RollupCell from "./virtualCell/RollupCell";
import FormulaCell from "./virtualCell/FormulaCell";
import hasManyCell from "./virtualCell/HasManyCell";
import LookupCell from "./virtualCell/LookupCell";
import manyToManyCell from "./virtualCell/ManyToManyCell";
import belongsToCell from "./virtualCell/BelongsToCell";
// todo: optimize parent/child meta extraction
export default {
name: "VirtualCell",
components: {
RollupCell,
FormulaCell,
LookupCell,
belongsToCell,
manyToManyCell,
hasManyCell
},
props: {
breadcrumbs: {
type: Array,
default() {
return [];
}
},
column: [Object],
row: [Object],
nodes: [Object],
meta: [Object],
api: [Object, Function],
active: Boolean,
sqlUi: [Object, Function],
isNew: {
type: Boolean,
default: false
},
isForm: {
type: Boolean,
default: false
},
disabledColumns: Object,
hint: String,
isLocked: Boolean,
required: Boolean,
isPublic: Boolean,
metas: Object,
password: String
},
computed: {
hm() {
return this.column && this.column.uidt === UITypes.LinkToAnotherRecord && this.column.colOptions.type === "hm";
},
bt() {
return this.column && (this.column.uidt === UITypes.ForeignKey || this.column.uidt === UITypes.LinkToAnotherRecord) && this.column.colOptions.type === "bt";
},
mm() {
return this.column && this.column.uidt === UITypes.LinkToAnotherRecord && this.column.colOptions.type === "mm";
},
lookup() {
return this.column && this.column.uidt === UITypes.Lookup;
},
formula() {
return this.column && this.column.uidt === UITypes.Formula;
},
rollup() {
return this.column && this.column.uidt === UITypes.Rollup;
}
},
methods: {
async save(row) {
if (row && this.$refs.cell && this.$refs.cell.saveLocalState) {
try {
await this.$refs.cell.saveLocalState(row);
} catch (e) {
}
}
}
}
};
</script> -->
<style scoped> <style scoped>
.nc-hint { .nc-hint {
font-size: 0.61rem; font-size: 0.61rem;

5
packages/nc-gui-v2/lib/enums.ts

@ -48,3 +48,8 @@ export enum Language {
sl = 'Slovenščina', sl = 'Slovenščina',
pt_BR = 'Português (Brasil)', pt_BR = 'Português (Brasil)',
} }
export enum NavigateDir {
NEXT,
PREV,
}

Loading…
Cancel
Save