<template> <div class="grid-view__row" :class="{ 'grid-view__row--selected': row._.selectedBy.length > 0, 'grid-view__row--loading': row._.loading, 'grid-view__row--hover': row._.hover, 'grid-view__row--warning': !row._.matchFilters || !row._.matchSortings || !row._.matchSearch, }" @mouseover="$emit('row-hover', { row, value: true })" @mouseleave="$emit('row-hover', { row, value: false })" @contextmenu.prevent="$emit('row-context', { row, event: $event })" > <template v-if="includeRowDetails"> <div v-if="!row._.matchFilters || !row._.matchSortings || !row._.matchSearch" class="grid-view__row-warning" > <template v-if="!row._.matchFilters"> {{ $t('gridViewRow.rowNotMatchingFilters') }} </template> <template v-else-if="!row._.matchSearch"> {{ $t('gridViewRow.rowNotMatchingSearch') }} </template> <template v-else-if="!row._.matchSortings">{{ $t('gridViewRow.rowHasMoved') }}</template> </div> <div class="grid-view__column" :style="{ width: gridViewRowDetailsWidth + 'px' }" > <div class="grid-view__row-info" :class="{ 'grid-view__row-info--matches-search': row._.matchSearch && row._.fieldSearchMatches.includes('row_id'), }" > <div class="grid-view__row-count" :class="{ 'grid-view__row-count--small': row.id > 9999 }" :title="row.id" > {{ row.id }} </div> <div v-if="!readOnly && canDrag" class="grid-view__row-drag" @mousedown="startDragging($event, row)" ></div> <component :is="rowExpandButton" :row="row" @edit-modal="$emit('edit-modal', row)" ></component> </div> </div> </template> <!-- Somehow re-declaring all the events instead of using v-on="$listeners" speeds everything up because the rows don't need to be updated everytime a new one is rendered, which happens a lot when scrolling. --> <GridViewCell v-for="field in fields" :key="'row-field-' + row.id + '-' + field.id.toString()" :field="field" :row="row" :state="state" :multi-select-position="getMultiSelectPosition(row.id, field)" :read-only="readOnly" :style="{ width: fieldWidths[field.id] + 'px' }" @update="$emit('update', $event)" @edit="$emit('edit', $event)" @select="$emit('select', $event)" @unselect="$emit('unselect', $event)" @selected="$emit('selected', $event)" @unselected="$emit('unselected', $event)" @select-next="$emit('select-next', $event)" @cell-mousedown-left="$emit('cell-mousedown-left', { row, field })" @cell-mouseover="$emit('cell-mouseover', { row, field })" @cell-mouseup-left="$emit('cell-mouseup-left', { row, field })" ></GridViewCell> </div> </template> <script> import GridViewCell from '@baserow/modules/database/components/view/grid/GridViewCell' import gridViewHelpers from '@baserow/modules/database/mixins/gridViewHelpers' import GridViewRowExpandButton from '@baserow/modules/database/components/view/grid/GridViewRowExpandButton' export default { name: 'GridViewRow', components: { GridViewRowExpandButton, GridViewCell }, mixins: [gridViewHelpers], props: { row: { type: Object, required: true, }, fields: { type: Array, required: true, }, allFieldIds: { type: Array, required: true, }, fieldWidths: { type: Object, required: true, }, includeRowDetails: { type: Boolean, required: false, default: () => false, }, readOnly: { type: Boolean, required: true, }, canDrag: { type: Boolean, required: true, }, }, data() { return { // The state can be used by functional components to make changes to the dom. // This is for example used by the functional file field component to enable the // drop effect without having the cell selected. state: {}, // A list containing field id's of field cells that must not be converted to the // functional component even though the user has selected another cell. This is // for example used by the file field to finish the uploading task if the user // has selected another cell while uploading. alive: [], rowExpandButton: this.$registry .get('application', 'database') .getRowExpandButtonComponent(), } }, methods: { isCellSelected(fieldId) { return this.row._.selected && this.row._.selectedFieldId === fieldId }, selectCell(fieldId, rowId = this.row.id) { this.$store.dispatch(this.storePrefix + 'view/grid/setSelectedCell', { rowId, fieldId, }) }, // Return an object that represents if a cell is selected, // and it's current position in the selection grid getMultiSelectPosition(rowId, field) { const position = { selected: false, top: false, right: false, bottom: false, left: false, } if ( this.$store.getters[this.storePrefix + 'view/grid/isMultiSelectActive'] ) { const rowIndex = this.$store.getters[ this.storePrefix + 'view/grid/getMultiSelectRowIndexById' ](rowId) let fieldIndex = this.allFieldIds.findIndex((id) => field.id === id) fieldIndex += !field.primary ? 1 : 0 const [minRow, maxRow] = this.$store.getters[ this.storePrefix + 'view/grid/getMultiSelectRowIndexSorted' ] const [minField, maxField] = this.$store.getters[ this.storePrefix + 'view/grid/getMultiSelectFieldIndexSorted' ] if (rowIndex >= minRow && rowIndex <= maxRow) { if (fieldIndex >= minField && fieldIndex <= maxField) { position.selected = true if (rowIndex === minRow) { position.top = true } if (rowIndex === maxRow) { position.bottom = true } if (fieldIndex === minField) { position.left = true } if (fieldIndex === maxField) { position.right = true } } } } return position }, setState(value) { this.state = value }, addKeepAlive(fieldId) { if (!this.alive.includes(fieldId)) { this.alive.push(fieldId) } }, removeKeepAlive(fieldId) { const index = this.alive.findIndex((id) => id === fieldId) if (index > -1) { this.alive.splice(index, 1) } }, startDragging(event, row) { if (this.readOnly) { return } event.preventDefault() this.$emit('row-dragging', { row, event }) }, }, } </script>