Skip to content

Commit

Permalink
Merge pull request #3154 from storybooks/addon-storysource-select-sto…
Browse files Browse the repository at this point in the history
…ry-from-the-panel

[STORYSOURCE] Add ability to select stories from inside of the StoryPanel
  • Loading branch information
ndelangen authored Mar 6, 2018
2 parents cad6153 + e3e23bd commit c94bcdd
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 23 deletions.
1 change: 1 addition & 0 deletions addons/storysource/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/components": "^3.4.0-alpha.9",
"acorn": "^5.5.0",
"acorn-es7": "^0.1.0",
"acorn-jsx": "^4.1.1",
Expand Down
123 changes: 104 additions & 19 deletions addons/storysource/src/StoryPanel.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { RoutedLink } from '@storybook/components';
import jsx from 'react-syntax-highlighter/languages/prism/jsx';
import { darcula } from 'react-syntax-highlighter/styles/prism';
import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/prism-light';
Expand All @@ -9,7 +10,12 @@ import { EVENT_ID } from './';
registerLanguage('jsx', jsx);

const styles = {
selections: {
story: {
display: 'block',
textDecoration: 'none',
color: darcula['code[class*="language-"]'].color,
},
selectedStory: {
backgroundColor: 'rgba(255, 242, 60, 0.2)',
},
panel: {
Expand All @@ -18,22 +24,44 @@ const styles = {
};

export default class StoryPanel extends Component {
static areLocationsEqual(a, b) {
return (
a.startLoc.line === b.startLoc.line &&
a.startLoc.col === b.startLoc.col &&
a.endLoc.line === b.endLoc.line &&
a.endLoc.col === b.endLoc.col
);
}

static getLocationKeys(locationsMap) {
return locationsMap
? Array.from(Object.keys(locationsMap)).sort(
(key1, key2) => locationsMap[key1].startLoc.line - locationsMap[key2].startLoc.line
)
: [];
}

constructor(props) {
super(props);

this.state = { source: '// Here will be dragons 🐉' };

const { channel } = props;

channel.on(EVENT_ID, ({ source, location }) => {
channel.on(EVENT_ID, ({ source, currentLocation, locationsMap }) => {
const locationsKeys = StoryPanel.getLocationKeys(locationsMap);

this.setState({
source,
location,
currentLocation,
locationsMap,
locationsKeys,
});
});

this.setSelectedStoryRef = this.setSelectedStoryRef.bind(this);
this.lineRenderer = this.lineRenderer.bind(this);
this.clickOnStory = this.clickOnStory.bind(this);
}

componentDidUpdate() {
Expand All @@ -46,6 +74,14 @@ export default class StoryPanel extends Component {
this.selectedStoryRef = ref;
}

clickOnStory(kind, story) {
const { api } = this.props;

if (kind && story) {
api.selectStory(kind, story);
}
}

createPart(rows, stylesheet, useInlineStyles) {
return rows.map((node, i) =>
createElement({
Expand All @@ -57,29 +93,75 @@ export default class StoryPanel extends Component {
);
}

lineRenderer({ rows, stylesheet, useInlineStyles }) {
const { location } = this.state;
createStoryPart(rows, stylesheet, useInlineStyles, location, kindStory) {
const { currentLocation } = this.state;
const first = location.startLoc.line - 1;
const last = location.endLoc.line;

const storyRows = rows.slice(first, last);
const story = this.createPart(storyRows, stylesheet, useInlineStyles);
const storyKey = `${first}-${last}`;

if (StoryPanel.areLocationsEqual(location, currentLocation)) {
return (
<div key={storyKey} ref={this.setSelectedStoryRef} style={styles.selectedStory}>
{story}
</div>
);
}

if (location) {
const [selectedKind, selectedStory] = kindStory.split('@');
const url = `/?selectedKind=${selectedKind}&selectedStory=${selectedStory}`;

return (
<RoutedLink
href={url}
key={storyKey}
onClick={() => this.clickOnStory(selectedKind, selectedStory)}
style={styles.story}
>
{story}
</RoutedLink>
);
}

createParts(rows, stylesheet, useInlineStyles) {
const { locationsMap, locationsKeys } = this.state;

const parts = [];
let lastRow = 0;

locationsKeys.forEach(key => {
const location = locationsMap[key];
const first = location.startLoc.line - 1;
const last = location.endLoc.line;

const start = this.createPart(rows.slice(0, first), stylesheet, useInlineStyles);
const selected = this.createPart(rows.slice(first, last), stylesheet, useInlineStyles);
const end = this.createPart(rows.slice(last), stylesheet, useInlineStyles);
const start = this.createPart(rows.slice(lastRow, first), stylesheet, useInlineStyles);
const storyPart = this.createStoryPart(rows, stylesheet, useInlineStyles, location, key);

return (
<span>
{start}
<div ref={this.setSelectedStoryRef} style={styles.selections}>
{selected}
</div>
{end}
</span>
);
parts.push(start);
parts.push(storyPart);

lastRow = last;
});

const lastPart = this.createPart(rows.slice(lastRow), stylesheet, useInlineStyles);

parts.push(lastPart);

return parts;
}

lineRenderer({ rows, stylesheet, useInlineStyles }) {
const { locationsMap, locationsKeys } = this.state;

if (!locationsMap || !locationsKeys.length) {
return this.createPart(rows, stylesheet, useInlineStyles);
}

return this.createPart(rows, stylesheet, useInlineStyles);
const parts = this.createParts(rows, stylesheet, useInlineStyles);

return <span>{parts.map(part => part)}</span>;
}

render() {
Expand All @@ -98,6 +180,9 @@ export default class StoryPanel extends Component {
}

StoryPanel.propTypes = {
api: PropTypes.shape({
selectStory: PropTypes.func.isRequired,
}).isRequired,
channel: PropTypes.shape({
emit: PropTypes.func,
on: PropTypes.func,
Expand Down
4 changes: 2 additions & 2 deletions addons/storysource/src/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import StoryPanel from './StoryPanel';
import { ADDON_ID, PANEL_ID } from './';

export function register() {
addons.register(ADDON_ID, () => {
addons.register(ADDON_ID, api => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Story',
render: () => <StoryPanel channel={channel} />,
render: () => <StoryPanel channel={channel} api={api} />,
});
});
}
5 changes: 3 additions & 2 deletions addons/storysource/src/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ function getLocation(context, locationsMap) {

function setStorySource(context, source, locationsMap) {
const channel = addons.getChannel();
const location = getLocation(context, locationsMap);
const currentLocation = getLocation(context, locationsMap);

channel.emit(EVENT_ID, {
source,
location,
currentLocation,
locationsMap,
});
}

Expand Down

0 comments on commit c94bcdd

Please sign in to comment.