Skip to content
This repository has been archived by the owner on Nov 10, 2017. It is now read-only.

Commit

Permalink
Remove unused knobs from panel
Browse files Browse the repository at this point in the history
When a story re-renders if a certain knob is not used that will be removed from the panel. When it is used again its added back.

See example story named `dynamic knobs` for an example.
  • Loading branch information
Aruna Herath committed Oct 12, 2016
1 parent 023aa7c commit a3fed39
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 7 deletions.
31 changes: 31 additions & 0 deletions dist/KnobManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ var _deepEqual2 = _interopRequireDefault(_deepEqual);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
var PANEL_UPDATE_INTERVAL = 400;

var KnobManager = function () {
function KnobManager() {
(0, _classCallCheck3.default)(this, KnobManager);
Expand All @@ -45,6 +48,8 @@ var KnobManager = function () {
(0, _createClass3.default)(KnobManager, [{
key: 'knob',
value: function knob(name, options) {
this._mayCallChannel();

var knobStore = this.knobStore;
var existingKnob = knobStore.get(name);
// We need to return the value set by the knob editor via this.
Expand All @@ -66,6 +71,7 @@ var KnobManager = function () {
}, {
key: 'wrapStory',
value: function wrapStory(channel, storyFn, context) {
this.channel = channel;
var key = context.kind + ':::' + context.story;
var knobStore = this.knobStoreMap[key];

Expand All @@ -77,6 +83,31 @@ var KnobManager = function () {
var props = { context: context, storyFn: storyFn, channel: channel, knobStore: knobStore };
return _react2.default.createElement(_WrapStory2.default, props);
}
}, {
key: '_mayCallChannel',
value: function _mayCallChannel() {
var _this = this;

// Re rendering of the story may cause changes to the knobStore. Some new knobs maybe added and
// Some knobs may go unused. So we need to update the panel accordingly. For example remove the
// unused knobs from the panel. This function sends the `setKnobs` message to the channel
// triggering a panel re-render.

if (this.calling) {
// If a call to channel has already registered ignore this call.
// Once the previous call is completed all the changes to knobStore including the one that
// triggered this, will be added to the panel.
// This avoids emitting to the channel within very short periods of time.
return;
}
this.calling = true;

setTimeout(function () {
_this.calling = false;
// emit to the channel and trigger a panel re-render
_this.channel.emit('addon:knobs:setKnobs', _this.knobStore.getAll());
}, PANEL_UPDATE_INTERVAL);
}
}]);
return KnobManager;
}();
Expand Down
20 changes: 19 additions & 1 deletion dist/KnobStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", {
value: true
});

var _keys = require("babel-runtime/core-js/object/keys");

var _keys2 = _interopRequireDefault(_keys);

var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
Expand Down Expand Up @@ -31,14 +35,19 @@ var KnobStore = function () {
key: "set",
value: function set(key, value) {
this.store[key] = value;
this.store[key].used = true;
this.callbacks.forEach(function (cb) {
return cb();
});
}
}, {
key: "get",
value: function get(key) {
return this.store[key];
var knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}
}, {
key: "getAll",
Expand All @@ -50,6 +59,15 @@ var KnobStore = function () {
value: function reset() {
this.store = {};
}
}, {
key: "markAllUnused",
value: function markAllUnused() {
var _this = this;

(0, _keys2.default)(this.store).forEach(function (knobName) {
_this.store[knobName].used = false;
});
}
}, {
key: "subscribe",
value: function subscribe(cb) {
Expand Down
4 changes: 3 additions & 1 deletion dist/components/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ var Panel = function (_React$Component) {
value: function render() {
var knobs = this.state.knobs;

var knobsArray = (0, _keys2.default)(knobs).map(function (key) {
var knobsArray = (0, _keys2.default)(knobs).filter(function (key) {
return knobs[key].used;
}).map(function (key) {
return knobs[key];
});

Expand Down
2 changes: 2 additions & 0 deletions dist/components/WrapStory.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ var WrapStory = function (_React$Component) {
var _props2 = this.props;
var storyFn = _props2.storyFn;
var context = _props2.context;
var knobStore = _props2.knobStore;

knobStore.markAllUnused();
return storyFn(context);
}
}]);
Expand Down
14 changes: 13 additions & 1 deletion example/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ stories.add('simple example', () => (
stories.add('with all knobs', () => {
const name = text('Name', 'Tom Cary');
const dob = date('DOB', new Date('January 20 1887'));

const bold = boolean('Bold', false);
const color = select('Color', {
red: 'Red',
Expand Down Expand Up @@ -80,6 +80,18 @@ stories.add('dates Knob', () => {
)
})

stories.add('dynamic knobs', () => {
const showOptional = select('Show optional', ['yes', 'no'], 'yes')
return (
<div>
<div>
{text('compulsary', 'I must be here')}
</div>
{ showOptional==='yes' ? <div>{text('optional', 'I can disapear')}</div> : null }
</div>
)
})

stories.add('without any knob', () => (
<button>This is a button</button>
));
28 changes: 28 additions & 0 deletions src/KnobManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import WrapStory from './components/WrapStory';
import KnobStore from './KnobStore';
import deepEqual from 'deep-equal';

// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
const PANEL_UPDATE_INTERVAL = 400;

export default class KnobManager {
constructor() {
this.knobStore = null;
this.knobStoreMap = {};
}

knob(name, options) {
this._mayCallChannel();

const knobStore = this.knobStore;
const existingKnob = knobStore.get(name);
// We need to return the value set by the knob editor via this.
Expand All @@ -31,6 +36,7 @@ export default class KnobManager {
}

wrapStory(channel, storyFn, context) {
this.channel = channel;
const key = `${context.kind}:::${context.story}`;
let knobStore = this.knobStoreMap[key];

Expand All @@ -42,4 +48,26 @@ export default class KnobManager {
const props = { context, storyFn, channel, knobStore };
return (<WrapStory {...props} />);
}

_mayCallChannel() {
// Re rendering of the story may cause changes to the knobStore. Some new knobs maybe added and
// Some knobs may go unused. So we need to update the panel accordingly. For example remove the
// unused knobs from the panel. This function sends the `setKnobs` message to the channel
// triggering a panel re-render.

if (this.calling) {
// If a call to channel has already registered ignore this call.
// Once the previous call is completed all the changes to knobStore including the one that
// triggered this, will be added to the panel.
// This avoids emitting to the channel within very short periods of time.
return;
}
this.calling = true;

setTimeout(() => {
this.calling = false;
// emit to the channel and trigger a panel re-render
this.channel.emit('addon:knobs:setKnobs', this.knobStore.getAll());
}, PANEL_UPDATE_INTERVAL);
}
}
13 changes: 12 additions & 1 deletion src/KnobStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ export default class KnobStore {

set(key, value) {
this.store[key] = value;
this.store[key].used = true;
this.callbacks.forEach(cb => cb());
}

get(key) {
return this.store[key];
const knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}

getAll() {
Expand All @@ -25,6 +30,12 @@ export default class KnobStore {
this.store = {};
}

markAllUnused() {
Object.keys(this.store).forEach(knobName => {
this.store[knobName].used = false;
});
}

subscribe(cb) {
this.callbacks.push(cb);
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ export default class Panel extends React.Component {

render() {
const { knobs } = this.state;
const knobsArray = Object.keys(knobs).map(key => (knobs[key]));
const knobsArray = Object.keys(knobs)
.filter(key => (knobs[key].used))
.map(key => (knobs[key]));

if (knobsArray.length === 0) {
return (
Expand Down
3 changes: 2 additions & 1 deletion src/components/WrapStory.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export default class WrapStory extends React.Component {
}

render() {
const { storyFn, context } = this.props;
const { storyFn, context, knobStore } = this.props;
knobStore.markAllUnused();
return storyFn(context);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/tests/KnobManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('KnobManager', () => {
it('should contain the story and add correct props', () => {
const testManager = new KnobManager();

const testChannel = {};
const testChannel = { emit: () => {} };
const testStory = () => (<div id="test-story">Test Content</div>);
const testContext = {
kind: 'Foo',
Expand Down

0 comments on commit a3fed39

Please sign in to comment.