Skip to content

Commit 1b8a29d

Browse files
authored
feat(metadata-views): Show edit icon in metadata view (#1667)
1 parent 6faed59 commit 1b8a29d

File tree

2 files changed

+74
-22
lines changed

2 files changed

+74
-22
lines changed

src/features/metadata-based-view/MetadataBasedItemList.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// @flow strict
22

3-
import React, { type Element } from 'react';
3+
import React, { type Element, Fragment } from 'react';
44
import classNames from 'classnames';
55
import { injectIntl, type IntlShape } from 'react-intl';
66
import MultiGrid from 'react-virtualized/dist/es/MultiGrid/MultiGrid';
77
import AutoSizer from 'react-virtualized/dist/es/AutoSizer';
88
import getProp from 'lodash/get';
99
import FileIcon from '../../icons/file-icon';
10+
import IconPencil from '../../icons/general/IconPencil';
1011
import messages from '../../elements/common/messages';
1112
import PlainButton from '../../components/plain-button';
13+
import Tooltip from '../../components/tooltip';
1214
import { getFileExtension } from '../../utils/file';
1315
import './MetadataBasedItemList.scss';
1416

@@ -30,6 +32,7 @@ const HEADER_ROW_INDEX = 0;
3032
const MIN_METADATA_COLUMN_WIDTH = 250;
3133

3234
type State = {
35+
hoveredColumnIndex: number,
3336
hoveredRowIndex: number,
3437
};
3538

@@ -48,6 +51,7 @@ type CellRendererArgs = {
4851
};
4952

5053
type ColumnWidthCallback = ({ index: number }) => number;
54+
type GridCellData = Element<typeof FileIcon | typeof PlainButton | typeof Fragment>;
5155

5256
class MetadataBasedItemList extends React.Component<Props, State> {
5357
props: Props;
@@ -56,7 +60,9 @@ class MetadataBasedItemList extends React.Component<Props, State> {
5660
super(props);
5761

5862
this.state = {
59-
hoveredRowIndex: -1, // initial MultiGrid load
63+
// initial MultiGrid load
64+
hoveredRowIndex: -1,
65+
hoveredColumnIndex: -1,
6066
};
6167
}
6268

@@ -95,11 +101,16 @@ class MetadataBasedItemList extends React.Component<Props, State> {
95101
onItemClick(itemWithPreviewPermission);
96102
}
97103

98-
getGridCellData(columnIndex: number, rowIndex: number): Element<typeof FileIcon | typeof PlainButton> | string {
104+
getGridCellData(columnIndex: number, rowIndex: number): GridCellData {
99105
const {
100106
currentCollection: { items },
107+
intl,
101108
metadataColumnsToShow,
102109
}: Props = this.props;
110+
const { hoveredColumnIndex, hoveredRowIndex }: State = this.state;
111+
const isCellHovered = columnIndex === hoveredColumnIndex && rowIndex === hoveredRowIndex;
112+
const metadataColumn = metadataColumnsToShow[columnIndex - FIXED_COLUMNS_NUMBER];
113+
const isCellEditable = isCellHovered && !!getProp(metadataColumn, 'canEdit', false);
103114
const item = items[rowIndex - 1];
104115
const { name } = item;
105116
let cellData;
@@ -117,10 +128,19 @@ class MetadataBasedItemList extends React.Component<Props, State> {
117128
break;
118129
default: {
119130
const data = getProp(item, 'metadata.data', {});
120-
const mdFieldName = this.getMetadataColumnName(
121-
metadataColumnsToShow[columnIndex - FIXED_COLUMNS_NUMBER],
131+
const mdFieldName = this.getMetadataColumnName(metadataColumn);
132+
cellData = (
133+
<>
134+
{data[mdFieldName]}
135+
{isCellEditable && (
136+
<Tooltip text={intl.formatMessage(messages.editLabel)}>
137+
<PlainButton type="button">
138+
<IconPencil />
139+
</PlainButton>
140+
</Tooltip>
141+
)}
142+
</>
122143
);
123-
cellData = data[mdFieldName];
124144
}
125145
}
126146

@@ -143,9 +163,17 @@ class MetadataBasedItemList extends React.Component<Props, State> {
143163
return headerData;
144164
}
145165

146-
handleMouseEnter = (rowIndex: number): void => this.setState({ hoveredRowIndex: rowIndex });
166+
handleMouseEnter = (columnIndex: number, rowIndex: number): void =>
167+
this.setState({
168+
hoveredColumnIndex: columnIndex,
169+
hoveredRowIndex: rowIndex,
170+
});
147171

148-
handleMouseLeave = (): void => this.setState({ hoveredRowIndex: -1 });
172+
handleMouseLeave = (): void =>
173+
this.setState({
174+
hoveredRowIndex: -1,
175+
hoveredColumnIndex: -1,
176+
});
149177

150178
cellRenderer = ({ columnIndex, rowIndex, key, style }: CellRendererArgs): Element<'div'> => {
151179
const { hoveredRowIndex } = this.state;
@@ -168,7 +196,7 @@ class MetadataBasedItemList extends React.Component<Props, State> {
168196
className={classes}
169197
style={style}
170198
onMouseLeave={this.handleMouseLeave}
171-
onMouseEnter={() => this.handleMouseEnter(rowIndex)}
199+
onMouseEnter={() => this.handleMouseEnter(columnIndex, rowIndex)}
172200
>
173201
{data}
174202
</div>

src/features/metadata-based-view/__tests__/MetadataBasedItemList-test.js

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react';
22
import FileIcon from '../../../icons/file-icon';
3+
import IconPencil from '../../../icons/general/IconPencil';
34
import PlainButton from '../../../components/plain-button';
5+
import Tooltip from '../../../components/tooltip';
46

57
import { MetadataBasedItemListComponent as MetadataBasedItemList } from '../MetadataBasedItemList';
68

@@ -67,7 +69,7 @@ describe('features/metadata-based-view/MetadataBasedItemList', () => {
6769
`('getColumnWidth() for $desc', ({ columnIndex, columnWidth }) => {
6870
const availableWidth = 500; // width provided to AutoSizer Component
6971
const getWidth = instance.getColumnWidth(availableWidth);
70-
expect(getWidth({ index: columnIndex })).toEqual(columnWidth);
72+
expect(getWidth({ index: columnIndex })).toBe(columnWidth);
7173
});
7274
});
7375

@@ -83,8 +85,28 @@ describe('features/metadata-based-view/MetadataBasedItemList', () => {
8385
${2} | ${2} | ${'receipt'}
8486
${3} | ${2} | ${200}
8587
`('cellData for row: $rowIndex, column: $columnIndex', ({ columnIndex, rowIndex, cellData }) => {
88+
const editableColumnIndex = 3; // amount field is editable
89+
90+
if (columnIndex === editableColumnIndex) {
91+
// Set state reflecting mouse-over action for every cell in editable column
92+
instance.handleMouseEnter(columnIndex, rowIndex);
93+
}
94+
8695
const data = instance.getGridCellData(columnIndex, rowIndex);
87-
expect(data).toEqual(cellData);
96+
if (columnIndex < 2) {
97+
// i.e. FileIcon and FileName columns
98+
expect(data).toEqual(cellData);
99+
return;
100+
}
101+
102+
const wrap = mount(data);
103+
expect(wrap.contains(cellData.toString())).toBe(true);
104+
105+
if (columnIndex === editableColumnIndex) {
106+
// Expect edit icon for editable column
107+
expect(wrap.contains(Tooltip)).toBe(true);
108+
expect(wrap.contains(IconPencil)).toBe(true);
109+
}
88110
});
89111
});
90112

@@ -97,7 +119,7 @@ describe('features/metadata-based-view/MetadataBasedItemList', () => {
97119
${3} | ${'amount'}
98120
`('headerData for column $columnIndex', ({ columnIndex, headerData }) => {
99121
const data = instance.getGridHeaderData(columnIndex);
100-
expect(data).toEqual(headerData);
122+
expect(data).toBe(headerData);
101123
});
102124
});
103125

@@ -113,15 +135,16 @@ describe('features/metadata-based-view/MetadataBasedItemList', () => {
113135

114136
describe('handleMouseEnter()', () => {
115137
test('should handle mouse over event by setting state accordingly', () => {
116-
instance.handleMouseEnter(5);
117-
expect(instance.state.hoveredRowIndex).toEqual(5);
138+
instance.handleMouseEnter(5, 8);
139+
expect(instance.state.hoveredColumnIndex).toBe(5);
140+
expect(instance.state.hoveredRowIndex).toBe(8);
118141
});
119142
});
120143

121144
describe('handleMouseLeave()', () => {
122145
test('should handle mouse leave event by setting state accordingly', () => {
123146
instance.handleMouseLeave();
124-
expect(instance.state.hoveredRowIndex).toEqual(-1);
147+
expect(instance.state.hoveredRowIndex).toBe(-1);
125148
});
126149
});
127150

@@ -131,19 +154,20 @@ describe('features/metadata-based-view/MetadataBasedItemList', () => {
131154
[{ columnIndex: 1, rowIndex: 2, key: 'key', style: {} }, false, true],
132155
])('should have correct class names', (arg, hasFileIconClass, hasFileNameClass) => {
133156
const cell = shallow(instance.cellRenderer(arg));
134-
expect(cell.hasClass('bdl-MetadataBasedItemList-cell')).toEqual(true);
135-
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--fileIcon')).toEqual(hasFileIconClass);
136-
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--filename')).toEqual(hasFileNameClass);
157+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell')).toBe(true);
158+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--fileIcon')).toBe(hasFileIconClass);
159+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--filename')).toBe(hasFileNameClass);
137160
});
138161

139162
test('should have hovered class for adding background color on row hover', () => {
140-
const hoverCellIndex = 1;
141-
instance.handleMouseEnter(hoverCellIndex); // Hover over row
163+
const hoverRowIndex = 1;
164+
const hoverColumnIndex = 1;
165+
instance.handleMouseEnter(hoverColumnIndex, hoverRowIndex); // Hover over row
142166

143167
const cell = shallow(
144-
instance.cellRenderer({ columnIndex: 0, rowIndex: hoverCellIndex, key: 'key', style: {} }),
168+
instance.cellRenderer({ columnIndex: 0, rowIndex: hoverRowIndex, key: 'key', style: {} }),
145169
);
146-
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--hover')).toEqual(true);
170+
expect(cell.hasClass('bdl-MetadataBasedItemList-cell--hover')).toBe(true);
147171
});
148172
});
149173

0 commit comments

Comments
 (0)