Skip to content

Commit 0a81bc6

Browse files
authored
Merge pull request #1039 from Gongreg/master
[Addon-Knobs] Fixing performance issues.
2 parents 41cbc3a + ad08ab3 commit 0a81bc6

7 files changed

Lines changed: 61 additions & 23 deletions

File tree

addons/knobs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"babel-runtime": "^6.23.0",
3535
"deep-equal": "^1.0.1",
3636
"insert-css": "^1.0.0",
37+
"lodash.debounce": "^4.0.8",
3738
"moment": "^2.18.1",
3839
"prop-types": "^15.5.8",
3940
"react-color": "^2.11.4",

addons/knobs/src/KnobManager.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,12 @@ export default class KnobManager {
6565
return;
6666
}
6767
this.calling = true;
68+
const timestamp = +new Date();
6869

6970
setTimeout(() => {
7071
this.calling = false;
7172
// emit to the channel and trigger a panel re-render
72-
this.channel.emit('addon:knobs:setKnobs', this.knobStore.getAll());
73+
this.channel.emit('addon:knobs:setKnobs', { knobs: this.knobStore.getAll(), timestamp });
7374
}, PANEL_UPDATE_INTERVAL);
7475
}
7576
}

addons/knobs/src/components/Panel.js

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
3+
import debounce from 'lodash.debounce';
34
import PropForm from './PropForm';
45
import Types from './types';
56

7+
const getTimestamp = () => +new Date();
8+
69
const styles = {
710
panelWrapper: {
811
width: '100%',
@@ -44,35 +47,50 @@ export default class Panel extends React.Component {
4447
this.handleChange = this.handleChange.bind(this);
4548
this.setKnobs = this.setKnobs.bind(this);
4649
this.reset = this.reset.bind(this);
50+
this.setOptions = this.setOptions.bind(this);
4751

4852
this.state = { knobs: {} };
53+
this.options = {};
54+
55+
this.lastEdit = getTimestamp();
4956
this.loadedFromUrl = false;
5057
this.props.channel.on('addon:knobs:setKnobs', this.setKnobs);
58+
this.props.channel.on('addon:knobs:setOptions', this.setOptions);
5159
}
5260

5361
componentWillUnmount() {
5462
this.props.channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
5563
}
5664

57-
setKnobs(knobs) {
65+
setOptions(options = { debounce: false, timestamps: false }) {
66+
this.options = options;
67+
68+
if (options.debounce) {
69+
this.emitChange = debounce(this.emitChange, options.debounce.wait, { leading: options.debounce.leading });
70+
}
71+
}
72+
73+
setKnobs({ knobs, timestamp }) {
5874
const queryParams = {};
5975
const { api, channel } = this.props;
6076

61-
Object.keys(knobs).forEach(name => {
62-
const knob = knobs[name];
63-
// For the first time, get values from the URL and set them.
64-
if (!this.loadedFromUrl) {
65-
const urlValue = api.getQueryParam(`knob-${name}`);
66-
67-
if (urlValue !== undefined) {
68-
// If the knob value present in url
69-
knob.value = Types[knob.type].deserialize(urlValue);
70-
channel.emit('addon:knobs:knobChange', knob);
77+
if (!this.options.timestamps || !timestamp || this.lastEdit <= timestamp) {
78+
Object.keys(knobs).forEach(name => {
79+
const knob = knobs[name];
80+
// For the first time, get values from the URL and set them.
81+
if (!this.loadedFromUrl) {
82+
const urlValue = api.getQueryParam(`knob-${name}`);
83+
84+
if (urlValue !== undefined) {
85+
// If the knob value present in url
86+
knob.value = Types[knob.type].deserialize(urlValue);
87+
channel.emit('addon:knobs:knobChange', knob);
88+
}
7189
}
72-
}
7390

74-
queryParams[`knob-${name}`] = Types[knob.type].serialize(knob.value);
75-
});
91+
queryParams[`knob-${name}`] = Types[knob.type].serialize(knob.value);
92+
});
93+
}
7694

7795
this.loadedFromUrl = true;
7896
api.setQueryParams(queryParams);
@@ -83,8 +101,13 @@ export default class Panel extends React.Component {
83101
this.props.channel.emit('addon:knobs:reset');
84102
}
85103

104+
emitChange(changedKnob) {
105+
this.props.channel.emit('addon:knobs:knobChange', changedKnob);
106+
}
107+
86108
handleChange(changedKnob) {
87-
const { api, channel } = this.props;
109+
this.lastEdit = getTimestamp();
110+
const { api } = this.props;
88111
const { knobs } = this.state;
89112
const { name, type, value } = changedKnob;
90113
const newKnobs = { ...knobs };
@@ -99,7 +122,7 @@ export default class Panel extends React.Component {
99122
queryParams[`knob-${name}`] = Types[type].serialize(value);
100123

101124
api.setQueryParams(queryParams);
102-
channel.emit('addon:knobs:knobChange', changedKnob);
125+
this.setState({ knobs: newKnobs }, this.emitChange(changedKnob));
103126
}
104127

105128
render() {

addons/knobs/src/components/WrapStory.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ export default class WrapStory extends React.Component {
3333
this.props.knobStore.unsubscribe(this.setPaneKnobs);
3434
}
3535

36-
setPaneKnobs() {
36+
setPaneKnobs(timestamp = +new Date()) {
3737
const { channel, knobStore } = this.props;
38-
channel.emit('addon:knobs:setKnobs', knobStore.getAll());
38+
channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp });
3939
}
4040

4141
knobChanged(change) {
@@ -52,7 +52,7 @@ export default class WrapStory extends React.Component {
5252
const { knobStore, storyFn, context } = this.props;
5353
knobStore.reset();
5454
this.setState({ storyContent: storyFn(context) });
55-
this.setPaneKnobs();
55+
this.setPaneKnobs(false);
5656
}
5757

5858
render() {

addons/knobs/src/components/__tests__/Panel.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('Panel', () => {
4646
},
4747
};
4848

49-
setKnobsHandler(knobs);
49+
setKnobsHandler({ knobs, timestamp: +new Date() });
5050
const knobFromUrl = {
5151
name: 'foo',
5252
value: testQueryParams['knob-foo'],
@@ -95,7 +95,7 @@ describe('Panel', () => {
9595
// Make it act like that url params are already checked
9696
wrapper.instance().loadedFromUrl = true;
9797

98-
setKnobsHandler(knobs);
98+
setKnobsHandler({ knobs, timestamp: +new Date() });
9999
const knobFromStory = {
100100
'knob-foo': knobs.foo.value,
101101
'knob-baz': knobs.baz.value,

addons/knobs/src/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,12 @@ export function withKnobs(storyFn, context) {
5959
const channel = addons.getChannel();
6060
return manager.wrapStory(channel, storyFn, context);
6161
}
62+
63+
export function withKnobsOptions(options = {}) {
64+
return (...args) => {
65+
const channel = addons.getChannel();
66+
channel.emit('addon:knobs:setOptions', options);
67+
68+
return withKnobs(...args);
69+
};
70+
}

addons/knobs/storybook-addon-knobs.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ interface NumberOptions {
1717
step: number,
1818
}
1919

20+
interface withKnobs {
21+
(storyFn: Function, context: StoryContext): React.ReactElement<IWrapStoryProps>;
22+
}
23+
2024
export function knob<T>(name: string, options: KnobOption<T>): T;
2125

2226
export function text(name: string, value: string | null): string;
@@ -42,4 +46,4 @@ interface IWrapStoryProps {
4246
initialContent?: Object;
4347
}
4448

45-
export function withKnobs(storyFn: Function, context: StoryContext): React.ReactElement<IWrapStoryProps>;
49+
export function withKnobsOptions(options: Object): (storyFn: Function, context: StoryContext) => withKnobs;

0 commit comments

Comments
 (0)