diff --git a/package.json b/package.json
index e94888e4a..f9985fc5e 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"jest": "^23.6.0",
"jsdoc": "^3.5.5",
"lint-staged": "^7.0.0",
+ "lodash": "^4.17.11",
"material-design-icons": "^3.0.1",
"material-ui": "^0.20.0",
"object-assign": "4.1.1",
diff --git a/src/__tests__/memoizeOne.spec.js b/src/__tests__/memoizeOne.spec.js
new file mode 100644
index 000000000..b23b9f63b
--- /dev/null
+++ b/src/__tests__/memoizeOne.spec.js
@@ -0,0 +1,29 @@
+import memoizeOne from '../modules/memoizeOne';
+
+describe('memoizeOne', () => {
+ it('Should return the same value when called twice with shallow-equal arguments', () => {
+ const object = { a: 0, b: 0 };
+ const fn = x => x.a + x.b;
+ const memoizedFn = memoizeOne(fn);
+
+ const val0 = memoizedFn(object);
+ expect(val0).toBe(0);
+ object.a = 1; // maintain shallow equality
+ expect(fn(object)).toBe(1);
+ const val1 = memoizedFn(object);
+ expect(val1).toBe(0);
+ });
+
+ it('Should forget the first value when called thrice', () => {
+ const object = { a: 0, b: 0 };
+ const memoizedFn = memoizeOne(x => x.a + x.b);
+
+ const val0 = memoizedFn(object);
+ expect(val0).toBe(0);
+ object.a = 1; // maintain shallow equality
+ const val1 = memoizedFn({ a: 42, b: 84 }); // This will bump val0 out of the cache
+ expect(val1).toBe(42 + 84);
+ const val2 = memoizedFn(object);
+ expect(val2).toBe(1);
+ });
+});
diff --git a/src/components/Item/ProgressiveLoadingContainer.js b/src/components/Item/ProgressiveLoadingContainer.js
new file mode 100644
index 000000000..b26aa5536
--- /dev/null
+++ b/src/components/Item/ProgressiveLoadingContainer.js
@@ -0,0 +1,91 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import debounce from 'lodash/debounce';
+
+const defaultDebounceMs = 100;
+const defaultBufferPx = 0;
+const defaultInitialBufferFactor = 0.5;
+
+class ProgressiveLoadingContainer extends Component {
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ debounceMs: PropTypes.number,
+ bufferPx: PropTypes.number,
+ initialBufferFactor: PropTypes.number,
+ };
+ static defaultProps = {
+ debounceMs: defaultDebounceMs,
+ bufferPx: defaultBufferPx,
+ initialBufferFactor: defaultInitialBufferFactor,
+ };
+
+ state = {
+ shouldLoad: false,
+ };
+ containerRef = null;
+ shouldLoadHandler = null;
+
+ checkShouldLoad(customBufferPx) {
+ const bufferPx = customBufferPx || this.props.bufferPx;
+
+ if (!this.containerRef) {
+ return;
+ }
+
+ const rect = this.containerRef.getBoundingClientRect();
+ if (
+ rect.bottom > -bufferPx &&
+ rect.top < window.innerHeight + bufferPx
+ ) {
+ this.setState({
+ shouldLoad: true,
+ });
+
+ this.removeHandler();
+ }
+ }
+
+ registerHandler() {
+ this.shouldLoadHandler = debounce(
+ () => this.checkShouldLoad(),
+ this.props.debounceMs
+ );
+
+ window.addEventListener('scroll', this.shouldLoadHandler);
+ }
+ removeHandler() {
+ window.removeEventListener('scroll', this.shouldLoadHandler);
+ }
+
+ componentDidMount() {
+ this.registerHandler();
+
+ const initialBufferPx = this.props.initialBufferFactor
+ ? this.props.initialBufferFactor * window.innerHeight
+ : undefined;
+ this.checkShouldLoad(initialBufferPx);
+ }
+
+ componentWillUnmount() {
+ this.removeHandler();
+ }
+
+ render() {
+ const {
+ children,
+ debounceMs,
+ bufferPx,
+ initialBufferFactor,
+ ...props
+ } = this.props;
+ const { shouldLoad } = this.state;
+
+ return (
+
(this.containerRef = ref)} {...props}>
+ {shouldLoad && children}
+
+ );
+ }
+}
+
+export default ProgressiveLoadingContainer;
diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js
index e84111fdf..6650cf028 100644
--- a/src/components/Item/VisualizationItem/Item.js
+++ b/src/components/Item/VisualizationItem/Item.js
@@ -16,6 +16,9 @@ import VisualizationItemHeaderButtons from './ItemHeaderButtons';
import DefaultPlugin from './DefaultPlugin';
import { colors } from '../../../modules/colors';
import ChartPlugin from 'data-visualizer-plugin';
+import ProgressiveLoadingContainer from '../ProgressiveLoadingContainer';
+import uniqueId from 'lodash/uniqueId';
+import memoizeOne from '../../../modules/memoizeOne';
const styles = {
icon: {
@@ -44,6 +47,8 @@ export class Item extends Component {
showFooter: false,
};
+ getUniqueKey = memoizeOne(filter => uniqueId());
+
pluginCredentials = null;
onToggleFooter = () => {
@@ -123,46 +128,30 @@ export class Item extends Component {
/>
) : null;
- getPluginComponent = () => {
- const { item } = this.props;
- const elementId = getGridItemDomId(item.id);
-
+ getContentStyle = () => {
+ const { item, editMode } = this.props;
const PADDING_BOTTOM = 4;
- const contentStyle = !this.props.editMode
+ return !editMode
? {
height: item.originalHeight - HEADER_HEIGHT - PADDING_BOTTOM,
}
: null;
-
- switch (item.type) {
- case CHART: {
- return (
-
-
-
- );
- }
- default: {
- return (
-
-
-
- );
- }
- }
};
+ getPluginComponent = () =>
+ this.props.item.type === CHART ? (
+
+ ) : (
+
+ );
+
render() {
- const { item, editMode } = this.props;
+ const { item, editMode, itemFilter } = this.props;
const { showFooter } = this.state;
return (
@@ -172,7 +161,18 @@ export class Item extends Component {
actionButtons={this.getActionButtons()}
editMode={editMode}
/>
- {this.getPluginComponent()}
+
+ {this.getPluginComponent()}
+
{!editMode && showFooter ? : null}
);
diff --git a/src/components/ItemGrid/ItemGrid.css b/src/components/ItemGrid/ItemGrid.css
index 5ee208939..3996f18dc 100644
--- a/src/components/ItemGrid/ItemGrid.css
+++ b/src/components/ItemGrid/ItemGrid.css
@@ -71,7 +71,7 @@
/* dashboard item - content */
.dashboard-item-content {
- padding: 0 4px 4px;
+ margin: 0 4px 4px;
overflow: auto;
}
diff --git a/src/modules/memoizeOne.js b/src/modules/memoizeOne.js
new file mode 100644
index 000000000..4e42a1ab5
--- /dev/null
+++ b/src/modules/memoizeOne.js
@@ -0,0 +1,23 @@
+// Note that this ignores discrepancies in 'this', so shouldn't be used with bound functions
+// This is useful instead of lodash/memoize when we only need to memoize a single value
+// Inspiration: https://github.com/alexreardon/memoize-one
+
+const memoizeOne = fn => {
+ let lastArgs = undefined;
+ let lastValue = undefined;
+
+ return (...args) => {
+ if (
+ lastArgs &&
+ args.length === lastArgs.length &&
+ args.every((arg, i) => arg === lastArgs[i])
+ ) {
+ return lastValue;
+ }
+ lastArgs = args;
+ lastValue = fn(...args);
+ return lastValue;
+ };
+};
+
+export default memoizeOne;
diff --git a/yarn.lock b/yarn.lock
index aa72adf83..a298184b4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11069,11 +11069,6 @@ webpack-sources@^1.0.1:
source-list-map "^2.0.0"
source-map "~0.6.1"
-webpack-stats-plugin@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.2.1.tgz#1f5bac13fc25d62cbb5fd0ff646757dc802b8595"
- integrity sha512-OYMZLpZrK/qLA79NE4kC4DCt85h/5ipvWJcsefKe9MMw0qU4/ck/IJg+4OmWA+5EfrZZpHXDq92IptfYDWVfkw==
-
webpack@3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83"