diff --git a/packages/nc-gui/components/dlg/KeyboardShortcuts.vue b/packages/nc-gui/components/dlg/KeyboardShortcuts.vue index 8079cd6bf8..d3cf53b122 100644 --- a/packages/nc-gui/components/dlg/KeyboardShortcuts.vue +++ b/packages/nc-gui/components/dlg/KeyboardShortcuts.vue @@ -15,6 +15,9 @@ const dialogShow = computed({ const renderCmdOrCtrlKey = () => { return isMac() ? '⌘' : 'CTRL' } +const renderAltOrOptlKey = () => { + return isMac() ? '⌥' : 'ALT' +} const shortcutList = [ { @@ -197,6 +200,22 @@ const shortcutList = [ keys: [renderCmdOrCtrlKey(), 'Enter'], behaviour: 'Save current expanded form item', }, + { + keys: [renderAltOrOptlKey(), '→'], + behaviour: 'Switch to next row', + }, + { + keys: [renderAltOrOptlKey(), '←'], + behaviour: 'Switch to previous row', + }, + { + keys: [renderAltOrOptlKey(), 'S'], + behaviour: 'Save current expanded form item', + }, + { + keys: [renderAltOrOptlKey(), 'N'], + behaviour: 'Create a new row', + }, ], }, ] diff --git a/packages/nc-gui/components/general/ShortcutLabel.vue b/packages/nc-gui/components/general/ShortcutLabel.vue new file mode 100644 index 0000000000..2e861cd948 --- /dev/null +++ b/packages/nc-gui/components/general/ShortcutLabel.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue index 87965126a7..d358804fa9 100644 --- a/packages/nc-gui/components/smartsheet/Grid.vue +++ b/packages/nc-gui/components/smartsheet/Grid.vue @@ -118,6 +118,7 @@ const { selectedAllRecords, removeRowIfNew, navigateToSiblingRow, + getExpandedRowIndex, } = useViewData(meta, view, xWhere) const { getMeta } = useMetas() @@ -980,6 +981,8 @@ const closeAddColumnDropdown = () => { :row-id="routeQuery.rowId" :view="view" show-next-prev-icons + :first-row="getExpandedRowIndex() === 0" + :last-row="getExpandedRowIndex() === data.length - 1" @next="navigateToSiblingRow(NavigateDir.NEXT)" @prev="navigateToSiblingRow(NavigateDir.PREV)" /> diff --git a/packages/nc-gui/components/smartsheet/expanded-form/Header.vue b/packages/nc-gui/components/smartsheet/expanded-form/Header.vue index 80ed48f93d..f453f8f8db 100644 --- a/packages/nc-gui/components/smartsheet/expanded-form/Header.vue +++ b/packages/nc-gui/components/smartsheet/expanded-form/Header.vue @@ -18,7 +18,7 @@ const route = useRoute() const { meta, isSqlView } = useSmartsheetStoreOrThrow() -const { commentsDrawer, displayValue, primaryKey, save: _save, loadRow } = useExpandedFormStoreOrThrow() +const { commentsDrawer, displayValue, primaryKey, save: _save, loadRow, saveRowAndStay } = useExpandedFormStoreOrThrow() const { isNew, syncLTARRefs, state } = useSmartsheetRowStoreOrThrow() @@ -26,8 +26,6 @@ const { isUIAllowed } = useUIPermission() const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) -const saveRowAndStay = ref(0) - const save = async () => { if (isNew.value) { const data = await _save(state.value) diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue index 20eae1d22b..f72807f0cf 100644 --- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue +++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue @@ -22,6 +22,7 @@ import { useVModel, watch, } from '#imports' +import { useActiveKeyupListener } from '~/composables/useSelectedCellKeyupListener' import type { Row } from '~/lib' interface Props { @@ -34,12 +35,16 @@ interface Props { rowId?: string view?: ViewType showNextPrevIcons?: boolean + firstRow?: boolean + lastRow?: boolean } const props = defineProps() const emits = defineEmits(['update:modelValue', 'cancel', 'next', 'prev']) +const key = ref(0) + const { t } = useI18n() const row = ref(props.row) @@ -64,7 +69,16 @@ const isKanban = inject(IsKanbanInj, ref(false)) provide(MetaInj, meta) -const { commentsDrawer, changedColumns, state: rowState, isNew, loadRow, save } = useProvideExpandedFormStore(meta, row) +const { + commentsDrawer, + changedColumns, + state: rowState, + isNew, + loadRow, + saveRowAndStay, + syncLTARRefs, + save, +} = useProvideExpandedFormStore(meta, row) const duplicatingRowInProgress = ref(false) @@ -157,6 +171,92 @@ const cellWrapperEl = ref() onMounted(() => { setTimeout(() => (cellWrapperEl.value?.querySelector('input,select,textarea') as HTMLInputElement)?.focus()) }) + +const addNewRow = () => { + setTimeout(async () => { + row.value = { + row: {}, + oldRow: {}, + rowMeta: { new: true }, + } + rowState.value = {} + key.value++ + isExpanded.value = true + }, 500) +} + +// attach keyboard listeners to switch between rows +// using alt + left/right arrow keys +useActiveKeyupListener( + isExpanded, + async (e: KeyboardEvent) => { + if (!e.altKey) return + if (e.key === 'ArrowLeft') { + e.stopPropagation() + emits('prev') + } else if (e.key === 'ArrowRight') { + e.stopPropagation() + emits('next') + } + // on alt + s save record + else if (e.code === 'KeyS') { + // remove focus from the active input if any + document.activeElement?.blur() + + e.stopPropagation() + + if (isNew.value) { + const data = await save(rowState.value) + await syncLTARRefs(data) + reloadHook?.trigger(null) + } else { + await save() + reloadHook?.trigger(null) + } + if (!saveRowAndStay.value) { + onClose() + } + // on alt + n create new record + } else if (e.code === 'KeyN') { + // remove focus from the active input if any to avoid unwanted input + ;(document.activeElement as HTMLInputElement)?.blur?.() + + if (changedColumns.value.size > 0) { + await Modal.confirm({ + title: 'Do you want to save the changes?', + okText: 'Save', + cancelText: 'Discard', + onOk: async () => { + await save() + reloadHook?.trigger(null) + addNewRow() + }, + onCancel: () => { + addNewRow() + }, + }) + } else if (isNew.value) { + await Modal.confirm({ + title: 'Do you want to save the record?', + okText: 'Save', + cancelText: 'Discard', + onOk: async () => { + const data = await save(rowState.value) + await syncLTARRefs(data) + reloadHook?.trigger(null) + addNewRow() + }, + onCancel: () => { + addNewRow() + }, + }) + } else { + addNewRow() + } + } + }, + { immediate: true }, +)