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
Andrew Clark committed
Aug 28, 2017 at 18:44 UTC
dd9db8b9d2de09f4b9e45fd03db58126253cb633
1 file changed
+173
-53
fixtures/attribute-behavior/src/App.js
+173
-53
@@ -2428,20 +2428,14 @@ const ALPHABETICAL = 'alphabetical';
2428
const REV_ALPHABETICAL = 'reverse_alphabetical';
2429
const GROUPED_BY_ROW_PATTERN = 'grouped_by_row_pattern';
2430
2431
-const attributesSorted = {
2432
- [ALPHABETICAL]: attributes
2433
- .slice(0)
2434
- .sort((attr1, attr2) => (attr1.name < attr2.name ? -1 : 1)),
2435
- [REV_ALPHABETICAL]: attributes
2436
- .slice(0)
2437
- .sort((attr1, attr2) => (attr1.name < attr2.name ? 1 : -1)),
2438
-};
2431
+const ALL = 'all';
2432
+const COMPLETE = 'complete';
2433
+const INCOMPLETE = 'incomplete';
2434
2435
function getCanonicalizedValue(value) {
2436
switch (typeof value) {
2437
case 'undefined':
2438
return '<undefined>';
2444
- break;
2439
case 'object':
2440
if (value === null) {
2441
return '<null>';
@@ -2522,7 +2516,6 @@ function getCanonicalizedValue(value) {
2516
default:
2517
throw new Error('Switch statement should be exhaustive.');
2518
}
2525
- return value;
2519
}
2520
2521
let _didWarn = false;
@@ -2582,6 +2575,8 @@ function getRenderedAttributeValue(renderer, attribute, type) {
2575
const result = read(container.firstChild);
2576
2577
return {
2578
+ tagName,
2579
+ containerTagName,
2580
testValue,
2581
defaultValue,
2582
result,
@@ -2592,6 +2587,8 @@ function getRenderedAttributeValue(renderer, attribute, type) {
2587
};
2588
} catch (error) {
2589
return {
2590
+ tagName,
2591
+ containerTagName,
2592
testValue,
2593
defaultValue,
2594
result: null,
@@ -2628,17 +2625,21 @@ function getRenderedAttributeValues(attribute, type) {
2625
}
2626
2627
const table = new Map();
2631
-const groupByRowPattern = new Map();
2628
+const rowPatternHashes = new Map();
2629
2633
-// Disable error overlay while test each attribute
2630
+// Disable error overlay while testing each attribute
2631
uninjectErrorOverlay();
2632
for (let attribute of attributes) {
2636
- const row = new Map();
2637
- let rowHash = '';
2633
+ const results = new Map();
2634
+ let hasSameBehaviorForAll = true;
2635
+ let rowPatternHash = '';
2636
for (let type of types) {
2637
const result = getRenderedAttributeValues(attribute, type);
2640
- row.set(type.name, result);
2641
- rowHash += [result.react15, result.react16]
2638
+ results.set(type.name, result);
2639
+ if (!result.hasSameBehavior) {
2640
+ hasSameBehaviorForAll = false;
2641
+ }
2642
+ rowPatternHash += [result.react15, result.react16]
2643
.map(res =>
2644
[
2645
res.canonicalResult,
@@ -2649,24 +2650,19 @@ for (let attribute of attributes) {
2650
)
2651
.join('||');
2652
}
2653
+ const row = {
2654
+ results,
2655
+ hasSameBehaviorForAll,
2656
+ rowPatternHash,
2657
+ // "Good enough" id that we can store in localStorage
2658
+ rowIdHash: `${attribute.name} ${attribute.tagName} ${attribute.overrideStringValue}`,
2659
+ };
2660
+ const rowGroup = rowPatternHashes.get(rowPatternHash) || new Set();
2661
+ rowGroup.add(row);
2662
+ rowPatternHashes.set(rowPatternHash, rowGroup);
2663
table.set(attribute, row);
2653
- if (!groupByRowPattern.get(rowHash)) {
2654
- groupByRowPattern.set(rowHash, []);
2655
- }
2656
- const updatedAttributesArray = groupByRowPattern.get(rowHash);
2657
- updatedAttributesArray.push(attribute);
2658
- groupByRowPattern.set(rowHash, updatedAttributesArray);
2664
}
2665
2661
-let attributesSortedByRowPattern = [];
2662
-groupByRowPattern.forEach(arrayOfAttributes => {
2663
- attributesSortedByRowPattern = attributesSortedByRowPattern.concat(
2664
- arrayOfAttributes
2665
- );
2666
-});
2667
-
2668
-attributesSorted[GROUPED_BY_ROW_PATTERN] = attributesSortedByRowPattern;
2669
-
2666
// Renable error overlay
2667
injectErrorOverlay();
2668
@@ -2827,7 +2823,7 @@ function ColumnHeader({children}) {
2823
);
2824
}
2825
2830
-function RowHeader({children}) {
2826
+function RowHeader({children, checked, onChange}) {
2827
return (
2828
<div
2829
css={{
@@ -2838,12 +2834,19 @@ function RowHeader({children}) {
2834
alignItems: 'center',
2835
}}>
2836
{children}
2837
+ <input type="checkbox" checked={checked} onChange={onChange} />
2838
</div>
2839
);
2840
}
2841
2842
function CellContent(props) {
2846
- const {columnIndex, rowIndex, attributesInSortedOrder} = props;
2843
+ const {
2844
+ columnIndex,
2845
+ rowIndex,
2846
+ attributesInSortedOrder,
2847
+ completedHashes,
2848
+ toggleAttribute,
2849
+ } = props;
2850
const attribute = attributesInSortedOrder[rowIndex - 1];
2851
const type = types[columnIndex - 1];
2852
@@ -2851,12 +2854,15 @@ function CellContent(props) {
2854
if (rowIndex === 0) {
2855
return null;
2856
}
2854
- const hasSameBehaviorForAll = types.every(
2855
- type => table.get(attribute).get(type.name).hasSameBehavior
2856
- );
2857
+ const row = table.get(attribute);
2858
+ const rowPatternHash = row.rowPatternHash;
2859
return (
2858
- <RowHeader>
2859
- {hasSameBehaviorForAll ? attribute.name : <b>{attribute.name}</b>}
2860
+ <RowHeader
2861
+ checked={completedHashes.has(rowPatternHash)}
2862
+ onChange={() => toggleAttribute(rowPatternHash)}>
2863
+ {row.hasSameBehaviorForAll
2864
+ ? attribute.name
2865
+ : <b css={{color: 'purple'}}>{attribute.name}</b>}
2866
</RowHeader>
2867
);
2868
}
@@ -2866,40 +2872,139 @@ function CellContent(props) {
2872
}
2873
2874
const row = table.get(attribute);
2869
- const result = row.get(type.name);
2875
+ const result = row.results.get(type.name);
2876
2877
return <Result {...result} />;
2878
}
2879
2874
-class App extends Component {
2875
- constructor() {
2876
- super();
2877
- this.state = {sortOrder: REV_ALPHABETICAL};
2878
- this._renderCell = this.renderCell.bind(this);
2879
- this._onUpdateSort = this.onUpdateSort.bind(this);
2880
+function saveToLocalStorage(completedHashes) {
2881
+ const str = JSON.stringify([...completedHashes]);
2882
+ localStorage.setItem('completedHashes', str);
2883
+}
2884
+
2885
+function restoreFromLocalStorage() {
2886
+ const str = localStorage.getItem('completedHashes');
2887
+ if (str) {
2888
+ const completedHashes = new Set(JSON.parse(str));
2889
+ return completedHashes;
2890
}
2891
+ return new Set();
2892
+}
2893
2882
- renderCell(props) {
2894
+class App extends Component {
2895
+ state = {
2896
+ sortOrder: ALPHABETICAL,
2897
+ filter: ALL,
2898
+ completedHashes: restoreFromLocalStorage(),
2899
+ };
2900
+
2901
+ renderCell = props => {
2902
return (
2903
<div style={props.style}>
2904
<CellContent
2886
- attributesInSortedOrder={attributesSorted[this.state.sortOrder]}
2905
+ toggleAttribute={this.toggleAttribute}
2906
+ completedHashes={this.state.completedHashes}
2907
+ attributesInSortedOrder={this.attributes}
2908
{...props}
2909
/>
2910
</div>
2911
);
2891
- }
2912
+ };
2913
2893
- onUpdateSort(e) {
2914
+ onUpdateSort = e => {
2915
this.setState({sortOrder: e.target.value});
2895
- this.grid.forceUpdateGrids();
2916
+ };
2917
+
2918
+ onUpdateFilter = e => {
2919
+ this.setState({filter: e.target.value});
2920
+ };
2921
+
2922
+ toggleAttribute = rowPatternHash => {
2923
+ const completedHashes = new Set(this.state.completedHashes);
2924
+ if (completedHashes.has(rowPatternHash)) {
2925
+ completedHashes.delete(rowPatternHash);
2926
+ } else {
2927
+ completedHashes.add(rowPatternHash);
2928
+ }
2929
+ this.setState({completedHashes}, () => saveToLocalStorage(completedHashes));
2930
+ };
2931
+
2932
+ componentWillMount() {
2933
+ this.attributes = this.getAttributes(
2934
+ this.state.sortOrder,
2935
+ this.state.filter
2936
+ );
2937
+ }
2938
+
2939
+ componentWillUpdate(nextProps, nextState) {
2940
+ if (
2941
+ nextState.sortOrder !== this.state.sortOrder ||
2942
+ nextState.filter !== this.state.filter ||
2943
+ nextState.completedHashes !== this.state.completedHashes
2944
+ ) {
2945
+ this.attributes = this.getAttributes(
2946
+ nextState.sortOrder,
2947
+ nextState.filter,
2948
+ nextState.completedHashes
2949
+ );
2950
+ this.grid.forceUpdateGrids();
2951
+ }
2952
+ }
2953
+
2954
+ getAttributes(sortOrder, filter, completedHashes) {
2955
+ // Filter
2956
+ let filteredAttributes;
2957
+ switch (filter) {
2958
+ case ALL:
2959
+ filteredAttributes = attributes.filter(() => true);
2960
+ break;
2961
+ case COMPLETE:
2962
+ filteredAttributes = attributes.filter(attribute => {
2963
+ const row = table.get(attribute);
2964
+ return completedHashes.has(row.rowPatternHash);
2965
+ });
2966
+ break;
2967
+ case INCOMPLETE:
2968
+ filteredAttributes = attributes.filter(attribute => {
2969
+ const row = table.get(attribute);
2970
+ return !completedHashes.has(row.rowPatternHash);
2971
+ });
2972
+ break;
2973
+ default:
2974
+ throw new Error('Switch statement should be exhuastive');
2975
+ }
2976
+
2977
+ // Sort
2978
+ switch (sortOrder) {
2979
+ case ALPHABETICAL:
2980
+ return filteredAttributes.sort(
2981
+ (attr1, attr2) => (attr1.name < attr2.name ? -1 : 1)
2982
+ );
2983
+ case REV_ALPHABETICAL:
2984
+ return filteredAttributes.sort(
2985
+ (attr1, attr2) => (attr1.name < attr2.name ? 1 : -1)
2986
+ );
2987
+ case GROUPED_BY_ROW_PATTERN: {
2988
+ return filteredAttributes.sort((attr1, attr2) => {
2989
+ const row1 = table.get(attr1);
2990
+ const row2 = table.get(attr2);
2991
+ const patternGroup1 = rowPatternHashes.get(row1.rowPatternHash);
2992
+ const patternGroupSize1 = (patternGroup1 && patternGroup1.size) || 0;
2993
+ const patternGroup2 = rowPatternHashes.get(row2.rowPatternHash);
2994
+ const patternGroupSize2 = (patternGroup2 && patternGroup2.size) || 0;
2995
+ return patternGroupSize2 - patternGroupSize1;
2996
+ });
2997
+ }
2998
+ default:
2999
+ throw new Error('Switch statement should be exhuastive');
3000
+ }
3001
}
3002
3003
render() {
3004
return (
3005
<div>
3006
<div>
2902
- <select onChange={this._onUpdateSort}>
3007
+ <select onChange={this.onUpdateSort}>
3008
<option
3009
selected={this.state.sortOrder === ALPHABETICAL}
3010
value={ALPHABETICAL}>
@@ -2916,6 +3021,21 @@ class App extends Component {
3021
grouped by row pattern :)
3022
</option>
3023
</select>
3024
+ <select onChange={this.onUpdateFilter}>
3025
+ <option selected={this.state.sortOrder === ALL} value={ALL}>
3026
+ all
3027
+ </option>
3028
+ <option
3029
+ selected={this.state.sortOrder === INCOMPLETE}
3030
+ value={INCOMPLETE}>
3031
+ incomplete
3032
+ </option>
3033
+ <option
3034
+ selected={this.state.sortOrder === COMPLETE}
3035
+ value={COMPLETE}>
3036
+ complete
3037
+ </option>
3038
+ </select>
3039
</div>
3040
<AutoSizer disableHeight={true}>
3041
{({width}) => (
@@ -2923,7 +3043,7 @@ class App extends Component {
3043
ref={input => {
3044
this.grid = input;
3045
}}
2926
- cellRenderer={this._renderCell}
3046
+ cellRenderer={this.renderCell}
3047
columnWidth={200}
3048
columnCount={1 + types.length}
3049
fixedColumnCount={1}
@@ -2931,7 +3051,7 @@ class App extends Component {
3051
enableFixedRowScroll={true}
3052
height={1200}
3053
rowHeight={40}
2934
- rowCount={attributes.length + 1}
3054
+ rowCount={this.attributes.length + 1}
3055
fixedRowCount={1}
3056
width={width}
3057
/>