Skip to content

Instantly share code, notes, and snippets.

@dabbott
Last active September 23, 2016 22:42
Show Gist options
  • Save dabbott/6274f21754ed6d925b7c515604feae9b to your computer and use it in GitHub Desktop.
Save dabbott/6274f21754ed6d925b7c515604feae9b to your computer and use it in GitHub Desktop.
Tab Content Management
// Goal:
// An extensible approach to show content in different kinds of views.
// The goal is that we can continue using this system when we have Deco plugins.
// Plugins can register themselves to load content.
//
// Use cases:
// - Show storyboards if a file ends in .storyboard.js
// - Allow the user to switch between viewing files as Storyboard/Text Editor, overriding the default viewer
//
// Future use cases:
// - Plugins which register viewers, e.g. image view for .jpg|.png
// - Non-file viewers, e.g. search results viewer, visual component map viewer
// Loader components export a registerLoader() method.
// To render content that uses this loader system, render <TabContent id={id} />,
// where id is the file id (or non-file id, when we have those)
// Register all loaders
// For now, registration order determines which loaders will be used by default when they pass the filter.
require('./Storyboard').registerLoader()
require('./Editor').registerLoader()
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as TabContentUtils from '../utils/TabContentUtils'
class Editor extends Component {
...
}
const ConnectedClass = connect()(Editor)
export default ConnectedClass
export const registerLoader = () => {
TabContentUtils.registerLoader(
'Text Editor',
(id) => true,
(id) => <ConnectedClass id={id} />
)
}
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as TabContentUtils from '../utils/TabContentUtils'
class Storyboard extends Component {
...
}
const ConnectedClass = connect()(Storyboard)
export default ConnectedClass
export const registerLoader = () => {
TabContentUtils.registerLoader(
'Storyboard',
(id) => id.endsWith('.storyboard.js'),
(id) => <ConnectedClass id={id} />
)
}
import React, { Component } from 'react'
import TabContent from './TabContent'
class TabbedEditor extends Component {
render() {
const {focusedTabId} = this.props
return <TabContent id={focusedTabId} />
}
}
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as TabContentUtils from '../utils/TabContentUtils'
// Can be more specific
const mapStateToProps = state => ({state: storeState})
class TabContent extends Component {
constructor(props) {
super()
this.state = this.mapPropsToState(props)
}
componentWillReceiveProps(nextProps) {
this.setState(this.mapPropsToState(nextProps))
}
mapPropsToState(props) {
return {loader: this.matchLoader(props)}
}
matchLoader(props) {
const {id, storeState} = props
return TabContentUtils.matchLoader(id, storeState)
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.id !== nextProps.id &&
this.state.loader !== nextState.loader
)
}
render() {
const {loader} = this.state
if (!loader) {
return <div>Failed to load content</div>
}
return loader.renderContent(id, loader)
}
}
export default connect(mapStateToProps)(TabContent)
const loaders = []
export const registerLoader = (name, filter, renderContent) => {
loaders.push({name, filter, renderContent})
}
// Determine how to display content
export const matchLoader = (id, state) => {
return loaders.find(loader => loader.filter(id, state))
}
// Used to give a list of all possible loaders, so the user can switch
// between multiple views of the same file (e.g. Text Editor vs. Storyboard)
export const matchLoaders = (id, state) => {
return loaders.filter(loader => loader.filter(id, state))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment