From dd9db8b9d2de09f4b9e45fd03db58126253cb633 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 28 Aug 2017 18:44:26 -0700 Subject: [PATCH] Add ability to "complete" rows and store state in localStorage (#10562) * Add ability to "complete" rows and store state in localStorage * Sort groups based on size Also: include tagName and containerTagName in info object. * Tweak sort order --- fixtures/attribute-behavior/src/App.js | 228 +++++++++++++++++++------ 1 file changed, 174 insertions(+), 54 deletions(-) diff --git a/fixtures/attribute-behavior/src/App.js b/fixtures/attribute-behavior/src/App.js index 8941d99bb3..f2843ead6d 100644 --- a/fixtures/attribute-behavior/src/App.js +++ b/fixtures/attribute-behavior/src/App.js @@ -2428,20 +2428,14 @@ const ALPHABETICAL = 'alphabetical'; const REV_ALPHABETICAL = 'reverse_alphabetical'; const GROUPED_BY_ROW_PATTERN = 'grouped_by_row_pattern'; -const attributesSorted = { - [ALPHABETICAL]: attributes - .slice(0) - .sort((attr1, attr2) => (attr1.name < attr2.name ? -1 : 1)), - [REV_ALPHABETICAL]: attributes - .slice(0) - .sort((attr1, attr2) => (attr1.name < attr2.name ? 1 : -1)), -}; +const ALL = 'all'; +const COMPLETE = 'complete'; +const INCOMPLETE = 'incomplete'; function getCanonicalizedValue(value) { switch (typeof value) { case 'undefined': return ''; - break; case 'object': if (value === null) { return ''; @@ -2522,7 +2516,6 @@ function getCanonicalizedValue(value) { default: throw new Error('Switch statement should be exhaustive.'); } - return value; } let _didWarn = false; @@ -2582,6 +2575,8 @@ function getRenderedAttributeValue(renderer, attribute, type) { const result = read(container.firstChild); return { + tagName, + containerTagName, testValue, defaultValue, result, @@ -2592,6 +2587,8 @@ function getRenderedAttributeValue(renderer, attribute, type) { }; } catch (error) { return { + tagName, + containerTagName, testValue, defaultValue, result: null, @@ -2628,17 +2625,21 @@ function getRenderedAttributeValues(attribute, type) { } const table = new Map(); -const groupByRowPattern = new Map(); +const rowPatternHashes = new Map(); -// Disable error overlay while test each attribute +// Disable error overlay while testing each attribute uninjectErrorOverlay(); for (let attribute of attributes) { - const row = new Map(); - let rowHash = ''; + const results = new Map(); + let hasSameBehaviorForAll = true; + let rowPatternHash = ''; for (let type of types) { const result = getRenderedAttributeValues(attribute, type); - row.set(type.name, result); - rowHash += [result.react15, result.react16] + results.set(type.name, result); + if (!result.hasSameBehavior) { + hasSameBehaviorForAll = false; + } + rowPatternHash += [result.react15, result.react16] .map(res => [ res.canonicalResult, @@ -2649,24 +2650,19 @@ for (let attribute of attributes) { ) .join('||'); } + const row = { + results, + hasSameBehaviorForAll, + rowPatternHash, + // "Good enough" id that we can store in localStorage + rowIdHash: `${attribute.name} ${attribute.tagName} ${attribute.overrideStringValue}`, + }; + const rowGroup = rowPatternHashes.get(rowPatternHash) || new Set(); + rowGroup.add(row); + rowPatternHashes.set(rowPatternHash, rowGroup); table.set(attribute, row); - if (!groupByRowPattern.get(rowHash)) { - groupByRowPattern.set(rowHash, []); - } - const updatedAttributesArray = groupByRowPattern.get(rowHash); - updatedAttributesArray.push(attribute); - groupByRowPattern.set(rowHash, updatedAttributesArray); } -let attributesSortedByRowPattern = []; -groupByRowPattern.forEach(arrayOfAttributes => { - attributesSortedByRowPattern = attributesSortedByRowPattern.concat( - arrayOfAttributes - ); -}); - -attributesSorted[GROUPED_BY_ROW_PATTERN] = attributesSortedByRowPattern; - // Renable error overlay injectErrorOverlay(); @@ -2827,7 +2823,7 @@ function ColumnHeader({children}) { ); } -function RowHeader({children}) { +function RowHeader({children, checked, onChange}) { return (
{children} +
); } function CellContent(props) { - const {columnIndex, rowIndex, attributesInSortedOrder} = props; + const { + columnIndex, + rowIndex, + attributesInSortedOrder, + completedHashes, + toggleAttribute, + } = props; const attribute = attributesInSortedOrder[rowIndex - 1]; const type = types[columnIndex - 1]; @@ -2851,12 +2854,15 @@ function CellContent(props) { if (rowIndex === 0) { return null; } - const hasSameBehaviorForAll = types.every( - type => table.get(attribute).get(type.name).hasSameBehavior - ); + const row = table.get(attribute); + const rowPatternHash = row.rowPatternHash; return ( - - {hasSameBehaviorForAll ? attribute.name : {attribute.name}} + toggleAttribute(rowPatternHash)}> + {row.hasSameBehaviorForAll + ? attribute.name + : {attribute.name}} ); } @@ -2866,40 +2872,139 @@ function CellContent(props) { } const row = table.get(attribute); - const result = row.get(type.name); + const result = row.results.get(type.name); return ; } -class App extends Component { - constructor() { - super(); - this.state = {sortOrder: REV_ALPHABETICAL}; - this._renderCell = this.renderCell.bind(this); - this._onUpdateSort = this.onUpdateSort.bind(this); - } +function saveToLocalStorage(completedHashes) { + const str = JSON.stringify([...completedHashes]); + localStorage.setItem('completedHashes', str); +} - renderCell(props) { +function restoreFromLocalStorage() { + const str = localStorage.getItem('completedHashes'); + if (str) { + const completedHashes = new Set(JSON.parse(str)); + return completedHashes; + } + return new Set(); +} + +class App extends Component { + state = { + sortOrder: ALPHABETICAL, + filter: ALL, + completedHashes: restoreFromLocalStorage(), + }; + + renderCell = props => { return (
); + }; + + onUpdateSort = e => { + this.setState({sortOrder: e.target.value}); + }; + + onUpdateFilter = e => { + this.setState({filter: e.target.value}); + }; + + toggleAttribute = rowPatternHash => { + const completedHashes = new Set(this.state.completedHashes); + if (completedHashes.has(rowPatternHash)) { + completedHashes.delete(rowPatternHash); + } else { + completedHashes.add(rowPatternHash); + } + this.setState({completedHashes}, () => saveToLocalStorage(completedHashes)); + }; + + componentWillMount() { + this.attributes = this.getAttributes( + this.state.sortOrder, + this.state.filter + ); } - onUpdateSort(e) { - this.setState({sortOrder: e.target.value}); - this.grid.forceUpdateGrids(); + componentWillUpdate(nextProps, nextState) { + if ( + nextState.sortOrder !== this.state.sortOrder || + nextState.filter !== this.state.filter || + nextState.completedHashes !== this.state.completedHashes + ) { + this.attributes = this.getAttributes( + nextState.sortOrder, + nextState.filter, + nextState.completedHashes + ); + this.grid.forceUpdateGrids(); + } + } + + getAttributes(sortOrder, filter, completedHashes) { + // Filter + let filteredAttributes; + switch (filter) { + case ALL: + filteredAttributes = attributes.filter(() => true); + break; + case COMPLETE: + filteredAttributes = attributes.filter(attribute => { + const row = table.get(attribute); + return completedHashes.has(row.rowPatternHash); + }); + break; + case INCOMPLETE: + filteredAttributes = attributes.filter(attribute => { + const row = table.get(attribute); + return !completedHashes.has(row.rowPatternHash); + }); + break; + default: + throw new Error('Switch statement should be exhuastive'); + } + + // Sort + switch (sortOrder) { + case ALPHABETICAL: + return filteredAttributes.sort( + (attr1, attr2) => (attr1.name < attr2.name ? -1 : 1) + ); + case REV_ALPHABETICAL: + return filteredAttributes.sort( + (attr1, attr2) => (attr1.name < attr2.name ? 1 : -1) + ); + case GROUPED_BY_ROW_PATTERN: { + return filteredAttributes.sort((attr1, attr2) => { + const row1 = table.get(attr1); + const row2 = table.get(attr2); + const patternGroup1 = rowPatternHashes.get(row1.rowPatternHash); + const patternGroupSize1 = (patternGroup1 && patternGroup1.size) || 0; + const patternGroup2 = rowPatternHashes.get(row2.rowPatternHash); + const patternGroupSize2 = (patternGroup2 && patternGroup2.size) || 0; + return patternGroupSize2 - patternGroupSize1; + }); + } + default: + throw new Error('Switch statement should be exhuastive'); + } } render() { return (
- +
{({width}) => ( @@ -2923,7 +3043,7 @@ class App extends Component { ref={input => { this.grid = input; }} - cellRenderer={this._renderCell} + cellRenderer={this.renderCell} columnWidth={200} columnCount={1 + types.length} fixedColumnCount={1} @@ -2931,7 +3051,7 @@ class App extends Component { enableFixedRowScroll={true} height={1200} rowHeight={40} - rowCount={attributes.length + 1} + rowCount={this.attributes.length + 1} fixedRowCount={1} width={width} />