@samitouri / QOS-React / commits / dd9db8b9d2

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 />