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

Remove unused knobs from panel #59

Merged
merged 1 commit into from
Oct 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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