1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-07 06:15:36 +00:00

Resolve "Introduce copy/paste functionality"

This commit is contained in:
Bram Wiepjes 2020-05-25 15:50:47 +00:00
parent f160dae1b0
commit 6e80cbc7a9
7 changed files with 176 additions and 42 deletions

View file

@ -23,3 +23,4 @@
* Use Django REST framework status code constants instead of integers.
* Added long text field.
* Fixed not refreshing token bug and improved authentication a little bit.
* Introduced copy, paste and delete functionality of selected fields.

View file

@ -13,6 +13,7 @@
<script>
import { isElement } from '@baserow/modules/core/utils/dom'
import { copyToClipboard } from '@baserow/modules/database/utils/clipboard'
export default {
name: 'GridViewField',
@ -105,37 +106,75 @@ export default {
}
document.body.addEventListener('click', this.$el.clickOutsideEvent)
// If the tab or arrow keys are pressed we want to select the next field. This
// is however out of the scope of this component so we emit the selectNext
// event that the GridView can handle.
this.$el.keyPressedNextFieldEvent = (event) => {
// We will first ask if we can select the next field. If that is not allowed
// we don't do anything.
if (!this.$refs.field.canSelectNext(event)) {
return
}
const { keyCode } = event
// Event that is called when a key is pressed while the field is selected.
this.$el.keyDownEvent = (event) => {
// If the tab or arrow keys are pressed we want to select the next field. This
// is however out of the scope of this component so we emit the selectNext
// event that the GridView can handle.
const { keyCode, ctrlKey, metaKey } = event
const arrowKeysMapping = {
37: 'selectPrevious',
38: 'selectAbove',
39: 'selectNext',
40: 'selectBelow',
}
if (Object.keys(arrowKeysMapping).includes(keyCode.toString())) {
if (
Object.keys(arrowKeysMapping).includes(keyCode.toString()) &&
this.$refs.field.canSelectNext(event)
) {
event.preventDefault()
this.$emit(arrowKeysMapping[keyCode])
}
if (keyCode === 9) {
if (keyCode === 9 && this.$refs.field.canSelectNext(event)) {
event.preventDefault()
this.$emit(event.shiftKey ? 'selectPrevious' : 'selectNext')
}
// Copies the value to the clipboard if ctrl/cmd + c is pressed.
if (
(ctrlKey || metaKey) &&
keyCode === 67 &&
this.$refs.field.canCopy(event)
) {
const rawValue = this.row['field_' + this.field.id]
const value = this.$registry
.get('field', this.field.type)
.prepareValueForCopy(this.field, rawValue)
copyToClipboard(value)
}
// Removes the value if the backspace/delete key is pressed.
if (
(keyCode === 46 || keyCode === 8) &&
this.$refs.field.canEmpty(event)
) {
event.preventDefault()
const value = this.$registry
.get('field', this.field.type)
.getEmptyValue(this.field)
const oldValue = this.row['field_' + this.field.id]
if (value !== oldValue) {
this.update(value, oldValue)
}
}
}
document.body.addEventListener(
'keydown',
this.$el.keyPressedNextFieldEvent
)
document.body.addEventListener('keydown', this.$el.keyDownEvent)
// Updates the value of the field when a user pastes something in the field.
this.$el.pasteEvent = (event) => {
if (!this.$refs.field.canPaste(event)) {
return
}
const value = this.$registry
.get('field', this.field.type)
.prepareValueForPaste(this.field, event.clipboardData)
const oldValue = this.row['field_' + this.field.id]
if (value !== oldValue) {
this.update(value, oldValue)
}
}
document.addEventListener('paste', this.$el.pasteEvent)
// Emit the selected event so that the parent component can take an action like
// making sure that the element fits in the viewport.
@ -152,11 +191,10 @@ export default {
this.selected = false
})
document.body.removeEventListener('click', this.$el.clickOutsideEvent)
document.body.removeEventListener(
'keydown',
this.$el.keyPressedNextFieldEvent
)
document.body.removeEventListener('keydown', this.$el.keyDownEvent)
document.removeEventListener('paste', this.$el.pasteEvent)
},
},
}
</script>
gl

View file

@ -116,6 +116,24 @@ export class FieldType extends Registerable {
toHumanReadableString(field, value) {
return value
}
/**
* This hook is called before the field's value is copied to the clipboard.
* Optionally formatting can be done here. By default the value is always
* converted to a string.
*/
prepareValueForCopy(field, value) {
return value.toString()
}
/**
* This hook is called before the field's value is overwritten by the clipboard
* data. That data might needs to be prepared so that the field accepts it.
* By default the text value if the clipboard data is used.
*/
prepareValueForPaste(field, clipboardData) {
return clipboardData.getData('text')
}
}
export class TextFieldType extends FieldType {
@ -194,6 +212,36 @@ export class NumberFieldType extends FieldType {
getRowEditFieldComponent() {
return RowEditFieldNumber
}
/**
* First checks if the value is numeric, if that is the case, the number is going
* to be formatted.
*/
prepareValueForPaste(field, clipboardData) {
const value = clipboardData.getData('text')
if (isNaN(parseFloat(value)) || !isFinite(value)) {
return null
}
return this.constructor.formatNumber(field, value)
}
/**
* Formats the value based on the field's settings. The number will be rounded
* if to much decimal places are provided and if negative numbers aren't allowed
* they will be set to 0.
*/
static formatNumber(field, value) {
if (value === '' || isNaN(value) || value === undefined || value === null) {
return null
}
const decimalPlaces =
field.number_type === 'DECIMAL' ? field.number_decimal_places : 0
let number = parseFloat(value)
if (!field.number_negative && number < 0) {
number = 0
}
return number.toFixed(decimalPlaces)
}
}
export class BooleanFieldType extends FieldType {
@ -220,4 +268,14 @@ export class BooleanFieldType extends FieldType {
getEmptyValue(field) {
return false
}
/**
* Check if the clipboard data text contains a string that might indicate if the
* value is true.
*/
prepareValueForPaste(field, clipboardData) {
const value = clipboardData.getData('text').toLowerCase()
const allowed = ['1', 'y', 't', 'y', 'yes', 'true', 'on']
return allowed.includes(value)
}
}

View file

@ -56,5 +56,31 @@ export default {
canSelectNext() {
return true
},
/**
* If the user presses ctrl/cmd + c while a field is selected, the value is
* going to be copied to the clipboard. In some cases, for example when the user
* is editing the value, we do not want to copy the value. If false is returned
* the value won't be copied.
*/
canCopy() {
return true
},
/**
* If the user presses ctrl/cmd + v while a field is selected, the value is
* overwritten with the data of the clipboard. In some cases, for example when the
* user is editing the value, we do not want to change the value. If false is
* returned the value won't be changed.
*/
canPaste() {
return true
},
/**
* If the user presses delete or backspace while a field is selected, the value is
* deleted. In some cases, for example when the user is editing the value, we do
* not want to delete the value. If false is returned the value won't be changed.
*/
canEmpty() {
return true
},
},
}

View file

@ -163,5 +163,14 @@ export default {
canSaveByPressingEnter(event) {
return true
},
canCopy() {
return !this.editing
},
canPaste() {
return !this.editing
},
canEmpty() {
return !this.editing
},
},
}

View file

@ -1,3 +1,5 @@
import { NumberFieldType } from '@baserow/modules/database/fieldTypes'
/**
* This mixin contains some method overrides for validating and formatting the
* number field. This mixin is used in both the GridViewFieldNumber and
@ -21,28 +23,11 @@ export default {
return this.getError() === null
},
/**
* Formats the value based on the field's settings. The number will be rounded
* if to much decimal places are provided and if negative numbers aren't allowed
* they will be set to 0.
* Before the numeric value is saved we might need to do some formatting such that
* the value is conform the fields requirements.
*/
beforeSave(value) {
if (
value === '' ||
isNaN(value) ||
value === undefined ||
value === null
) {
return null
}
const decimalPlaces =
this.field.number_type === 'DECIMAL'
? this.field.number_decimal_places
: 0
let number = parseFloat(value)
if (!this.field.number_negative && number < 0) {
number = 0
}
return number.toFixed(decimalPlaces)
return NumberFieldType.formatNumber(this.field, value)
},
},
}

View file

@ -0,0 +1,17 @@
/**
* Copies the given text to the clipboard by temporarily creating a textarea and
* using the documents `copy` command.
*/
export const copyToClipboard = (text) => {
const textarea = document.createElement('textarea')
document.body.appendChild(textarea)
textarea.style.position = 'absolute'
textarea.style.left = '-99999px'
textarea.style.top = '-99999px'
textarea.value = text
textarea.select()
document.execCommand('copy')
document.body.removeChild(textarea)
}