$30 off During Our Annual Pro Sale. View Details »

Smells in React Apps

Smells in React Apps

Presented at JSConf.Asia 2018

Thai Pangsakulyanont

January 27, 2018
Tweet

More Decks by Thai Pangsakulyanont

Other Decks in Technology

Transcript

  1. Slide № 5 Refactoring: Improving the Design of Existing Code

    Clean Code: A Handbook of Agile Software Craftsmanship Agile Software Development, Principles, Patterns, and Practices
  2. Slide № 25 Object-oriented analysis and design Clean architecture Functional

    programming Automated testing Continuous integration Linting Type systems Modularization Componentization Code smells …
  3. Slide № 54 Object-oriented analysis and design Clean architecture Functional

    programming Automated testing Continuous integration Linting Type systems Modularization Componentization Code smells …
  4. Slide № 55 Object-oriented analysis and design Clean architecture Functional

    programming Automated testing Continuous integration Linting Type systems Modularization Componentization Code smells … Tech stuffs
  5. Slide № 58 // MusicPreviewer.js export function preload () {

    getInstance() } export function enable () { return getInstance().enable() } export function disable () { return getInstance().disable() } export function go () { return getInstance().go() } export function preview (url) { return getInstance().preview(url) } MusicPreviewer
  6. Slide № 59 // MusicPreviewer.js export function preload () {

    getInstance() } export function enable () { return getInstance().enable() } export function disable () { return getInstance().disable() } export function go () { return getInstance().go() } export function preview (url) { return getInstance().preview(url) } MusicPreviewer exports 5 functions
  7. Slide № 60 // MusicPreviewer.js export function preload () {

    getInstance() } export function enable () { return getInstance().enable() } export function disable () { return getInstance().disable() } export function go () { return getInstance().go() } export function preview (url) { return getInstance().preview(url) } import * as MusicPreviewer from MusicPreviewer.preload() MusicPreviewer.enable() MusicPreviewer.disable() MusicPreviewer.go() MusicPreviewer.preview(url) User code:
  8. Slide № 62 let instance const getInstance = () =>

    instance || ( instance = createMusicPreviewer() ) function createMusicPreviewer () { let enabled = false let currentUrl = null let backgroundLoaded = false let backgroundPlayed = false const instances = {} function update () { if (!enabled) { if (backgroundPlayed) { backgroundFader.fadeTo(0, 100) backgroundPlayed = false background.pause() } for (const key of Object.keys(instances)) { const instance = instances[key] instance.destroy() } return } let playing = null for (const key of Object.keys(instances)) { const instance = instances[key] if (key === currentUrl) { if (instance.loaded) { instance.play() playing = instance } else { instance.stop() } } if (playing) { backgroundFader.fadeTo(0, 1) } else { backgroundFader.fadeTo(0.4, 0.5) if (backgroundLoaded && !backgroundPlayed) { backgroundPlayed = true try { background.play() } catch (e) { console.warn('Cannot play background music') } } } } const musicPreviewer = { enable () { if (enabled) return enabled = true update() }, disable () { if (!enabled) return enabled = false update() }, go () { if (!enabled) return goSound.currentTime = 0
  9. Slide № 63 Wrote a hack behind a well-designed interface

    In principle: Bad code stays behind that interface.
  10. Slide № 64 Wrote a hack behind a well-designed interface

    In principle: Bad code stays behind that interface. In practice: Another person looks for a technical solution,
  11. Slide № 65 Wrote a hack behind a well-designed interface

    In principle: Bad code stays behind that interface. In practice: Another person looks for a technical solution, found my hack (but didn’t know it was a hack),
  12. Slide № 66 Wrote a hack behind a well-designed interface

    In principle: Bad code stays behind that interface. In practice: Another person looks for a technical solution, found my hack (but didn’t know it was a hack), and duplicated the code!
  13. Slide № 75 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component
  14. Slide № 76 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component
  15. Slide № 78 Thai Pangsakulyanont 1 minute ago Let’s go

    to the Marina Bay Carnival after the conference!!!!
  16. Slide № 80 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component
  17. Slide № 81 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component
  18. Slide № 82 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component
  19. Slide № 83 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component <UserAvatar size='message' … />
  20. Slide № 92 UserAvatar uses specific feature generic component I

    know how big to display in a message Message
  21. Slide № 93 UserAvatar uses aware of specific feature generic

    component Message I know how big to display in a message
  22. Slide № 95 UserAvatar Message uses aware of specific feature

    generic component Information leaking. Knows how to render itself Except: Doesn’t know the appropriate size of the avatar
  23. Slide № 96 UserAvatar Message uses aware of specific feature

    generic component Information leaking. Knows how to render itself Except: Doesn’t know the appropriate size of the avatar The knowledge of the size, which should have belonged to the messaging system is here
  24. Slide № 100 <UserAvatar size='small' … /> <UserAvatar size='medium' …

    /> <UserAvatar size='large' … /> A UserAvatar component <UserAvatar size='message' … />
  25. Slide № 102 UserAvatar specific feature generic component I need

    to show an avatar! It’s going to have size M! Message
  26. Slide № 103 A UserAvatar component function UserAvatar (props) {

    … } UserAvatar.propTypes = { size: PropTypes.oneOf([ 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XX … }
  27. Slide № 104 A UserAvatar component function UserAvatar (props) {

    … } UserAvatar.propTypes = { size: PropTypes.oneOf([ 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XX … }
  28. Slide № 105 A UserAvatar component function UserAvatar (props) {

    … } UserAvatar.propTypes = { size: PropTypes.number, … }
  29. Slide № 106 A UserAvatar component function UserAvatar (props) {

    … } UserAvatar.propTypes = { size: PropTypes.oneOf([ 18, 24, 30, 36, 48, … ]), … }
  30. Slide № 114 Upload file Select file on computer… Dropbox

    Google Drive Emergency alert Select file on computer…
  31. Slide № 115 Upload file Select file on computer… Dropbox

    Google Drive Emergency alert <Button>
  32. Slide № 116 Upload file Select file on computer… Dropbox

    Google Drive Emergency alert <Menu> <Button>
  33. Slide № 118 Select file on computer… Dropbox Google Drive

    Emergency alert <MenuButton> Upload file
  34. Slide № 121 Select file on computer… Dropbox Google Drive

    Emergency alert <MenuButton> Upload file
  35. Slide № 122 Select file on computer… Dropbox Google Drive

    Emergency alert <MenuButton> Upload file Select file on computer…
  36. Slide № 123 Select file on computer… Dropbox Google Drive

    Emergency alert <MenuButton> Upload file Select file on computer… Button must appear to be pressed while menu is open
  37. Slide № 139 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> </Navbar> ) }
  38. Slide № 140 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> </Navbar> ) }
  39. Slide № 141 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> </Navbar> ) }
  40. Slide № 146 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> </Navbar> ) } }
  41. Slide № 147 class Navigation extends Component { 
 render

    () { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav>
  42. Slide № 148 class Navigation extends Component {
 static propTypes

    = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav>
  43. Slide № 149 class Navigation extends Component { static propTypes

    = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav>
  44. Slide № 150 const loggedIn = !!this.props.currentUser return ( <Navbar>

    <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> </Navbar> ) } }
  45. Slide № 151 const loggedIn = !!this.props.currentUser return ( <Navbar>

    <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> <Nav pullRight> </Nav> </Navbar> ) } }
  46. Slide № 152 const loggedIn = !!this.props.currentUser return ( <Navbar>

    <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> <Nav pullRight> {!loggedIn && <NavItem href='#' onClick={this.props.onLogin}>Login</NavI {loggedIn && <NavItem href='#' onClick={this.props.onLogout}>Logout</Nav </Nav> </Navbar> ) } }
  47. Slide № 153 class Navigation extends Component { static propTypes

    = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header>
  48. Slide № 154 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header>
  49. Slide № 155 const enhance = connect( state => ({

    currentUser: selectCurrentUser(props) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header>
  50. Slide № 156 const enhance = connect( state => ({

    currentUser: selectCurrentUser(props) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> Selecting data from store
  51. Slide № 157 const enhance = connect( state => ({

    currentUser: selectCurrentUser(props) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> Selecting data from store Dispatching action to store
  52. Slide № 158 const enhance = connect( state => ({

    currentUser: selectCurrentUser(props) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> Selecting data from store Dispatching action to store Rendering logic
  53. Slide № 163 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header>
  54. Slide № 164 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> Selecting data from store Dispatching action to store Rendering logic
  55. Slide № 165 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> Selecting data from store Dispatching action to store Rendering logic Go with the flow!
  56. Slide № 166 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header>
  57. Slide № 167 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser
  58. Slide № 168 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser
  59. Slide № 169 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser
  60. Slide № 170 onViewMessages: MessagesIO.viewMessages } ) class Navigation extends

    Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem>
  61. Slide № 171 onViewMessages: MessagesIO.viewMessages } ) class Navigation extends

    Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header>
  62. Slide № 172 onViewMessages: MessagesIO.viewMessages } ) class Navigation extends

    Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header>
  63. Slide № 173 <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand>

    </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> <Nav pullRight> {!loggedIn && <NavItem href='#' onClick={this.props.onLogin}>Login</NavI {loggedIn && <NavItem href='#' onClick={this.props.onLogout}>Logout</Nav </Nav> </Navbar> ) } }
  64. Slide № 174 <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand>

    </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> </Nav> <Nav pullRight>
  65. Slide № 175 <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand>

    </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) </NavItem> } </Nav> <Nav pullRight>
  66. Slide № 176 <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand>

    </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) </NavItem> } {loggedIn && <NavItem href='/inbox' onClick={this.props.onViewMessages}> Messages ({this.props.newMessageCount}) </NavItem> } </Nav> <Nav pullRight>
  67. Slide № 177 <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand>

    </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) </NavItem> } {loggedIn && <NavItem href='/inbox' onClick={this.props.onViewMessages}> Messages ({this.props.newMessageCount}) </NavItem> } </Nav> <Nav pullRight>
  68. Slide № 178 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount})
  69. Slide № 179 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) Selecting data from store Dispatching action to store Rendering logic
  70. Slide № 180 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) Selecting data from store Dispatching action to store Rendering logic Cross-cutting concerns
  71. Slide № 181 User Notification Message User Message User Notification

    Message User Navigation Navigation User Navigation const enhance = connect( state => ({ currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount})
  72. Slide № 182 User Notification Message User Message User Notification

    Message User Navigation Navigation User Navigation const enhance = connect( state => ({ currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount})
  73. Slide № 186 diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx index a2e1d2e..2333c9f 100644

    --- src/app/ui/OptionsPlayer.jsx +++ src/app/ui/OptionsPlayer.jsx @@ -1,176 +1,191 @@ const enhance = compose( connect((state) => ({ options: state.options, scratch: Options.scratchPosition(state.options), })), connectIO({ onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }), onSetScratch: () => (position) => OptionsIO.setScratch(position), onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed), onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime), onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover), onToggleBackgroundAnimationsEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'system.bga.enabled': Options.toggleOption(options['system.bga.enabled']) }) ), onToggleAutoVelocityEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity']) }) ) }), pure ) export const OptionsPlayer = React.createClass({ propTypes: { options: React.PropTypes.object, scratch: React.PropTypes.string, onClose: React.PropTypes.func, onSetPanel: React.PropTypes.func, onSetScratch: React.PropTypes.func, onSetSpeed: React.PropTypes.func, onSetLaneCover: React.PropTypes.func, onToggleBackgroundAnimationsEnabled: React.PropTypes.func, onToggleAutoVelocityEnabled: React.PropTypes.func, onSetLeadTime: React.PropTypes.func }, render () { return <div className="OptionsPlayer"> <OptionsPlayer.Row label="Speed" hidden={Options.isAutoVelocityEnabled(this.props.options)} > <OptionsSpeed value={this.props.options['player.P1.speed']} onChange={this.props.onSetSpeed} /> <div className="OptionsPlayerͷhelp"> You can also change the speed in-game<br />using the Up and Down arrow keys. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="LeadTime" hidden={!Options.isAutoVelocityEnabled(this.props.options)} > <OptionsLeadTimeInputField value={Options.leadTime(this.props.options)} onChange={this.props.onSetLeadTime} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp"> Speed will be automatically adjusted<br />to maintain a consistent note velocity. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="Scratch"> <OptionsPlayerSelector type="scratch" options={SCRATCH_OPTIONS} onSelect={this.props.onSetScratch} value={this.props.scratch} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Panel"> <OptionsPlayerSelector type="panel" options={PANEL_OPTIONS} onSelect={this.props.onSetPanel} value={this.props.options['player.P1.panel']} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Cover"> <OptionsLaneCoverInputField value={Options.laneCover(this.props.options)} onChange={this.props.onSetLaneCover} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp" title="Can be negative, in this case the play area is pulled up." > The amount of play area to hide from the top. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="BGA"> <OptionsCheckbox checked={Options.isBackgroundAnimationsEnabled(this.props.options)} onToggle={this.props.onToggleBackgroundAnimationsEnabled} > Enable background animations <span className="OptionsPlayerͷhint">(720p, alpha)</span> </OptionsCheckbox> </OptionsPlayer.Row> <OptionsPlayer.Row label="AutoVel"> <OptionsCheckbox checked={Options.isAutoVelocityEnabled(this.props.options)} onToggle={this.props.onToggleAutoVelocityEnabled} > Maintain absolute note velocity <span className="OptionsPlayerͷhint">(advanced)</span> </OptionsCheckbox> </OptionsPlayer.Row> <div className="OptionsPlayerͷbuttons"> <OptionsButton onClick={this.props.onClose}>Save & Exit</OptionsButton> </div> </div> } })
  74. Slide № 187 diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx index a2e1d2e..2333c9f 100644

    --- src/app/ui/OptionsPlayer.jsx +++ src/app/ui/OptionsPlayer.jsx @@ -1,176 +1,191 @@ const enhance = compose( connect((state) => ({ options: state.options, scratch: Options.scratchPosition(state.options), })), connectIO({ onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }), onSetScratch: () => (position) => OptionsIO.setScratch(position), onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed), onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime), onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover), onToggleBackgroundAnimationsEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'system.bga.enabled': Options.toggleOption(options['system.bga.enabled']) }) ), onToggleAutoVelocityEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity']) }) + ), + onToggleGauge: ({ options }) => () => ( + OptionsIO.setOptions({ + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge']) + }) ) }), pure ) export const OptionsPlayer = React.createClass({ propTypes: { options: React.PropTypes.object, scratch: React.PropTypes.string, onClose: React.PropTypes.func, onSetPanel: React.PropTypes.func, onSetScratch: React.PropTypes.func, onSetSpeed: React.PropTypes.func, onSetLaneCover: React.PropTypes.func, onToggleBackgroundAnimationsEnabled: React.PropTypes.func, onToggleAutoVelocityEnabled: React.PropTypes.func, onSetLeadTime: React.PropTypes.func }, render () { return <div className="OptionsPlayer"> <OptionsPlayer.Row label="Speed" hidden={Options.isAutoVelocityEnabled(this.props.options)} > <OptionsSpeed value={this.props.options['player.P1.speed']} onChange={this.props.onSetSpeed} /> <div className="OptionsPlayerͷhelp"> You can also change the speed in-game<br />using the Up and Down arrow keys. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="LeadTime" hidden={!Options.isAutoVelocityEnabled(this.props.options)} > <OptionsLeadTimeInputField value={Options.leadTime(this.props.options)} onChange={this.props.onSetLeadTime} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp"> Speed will be automatically adjusted<br />to maintain a consistent note velocity. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="Scratch"> <OptionsPlayerSelector type="scratch" options={SCRATCH_OPTIONS} onSelect={this.props.onSetScratch} value={this.props.scratch} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Panel"> <OptionsPlayerSelector type="panel" options={PANEL_OPTIONS} onSelect={this.props.onSetPanel} value={this.props.options['player.P1.panel']} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Cover"> <OptionsLaneCoverInputField value={Options.laneCover(this.props.options)} onChange={this.props.onSetLaneCover} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp" title="Can be negative, in this case the play area is pulled up." > The amount of play area to hide from the top. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="BGA"> <OptionsCheckbox checked={Options.isBackgroundAnimationsEnabled(this.props.options)} onToggle={this.props.onToggleBackgroundAnimationsEnabled} > Enable background animations <span className="OptionsPlayerͷhint">(720p, alpha)</span> </OptionsCheckbox> </OptionsPlayer.Row> <OptionsPlayer.Row label="AutoVel"> <OptionsCheckbox checked={Options.isAutoVelocityEnabled(this.props.options)} onToggle={this.props.onToggleAutoVelocityEnabled} > Maintain absolute note velocity <span className="OptionsPlayerͷhint">(advanced)</span> </OptionsCheckbox> </OptionsPlayer.Row> <div className="OptionsPlayerͷbuttons"> <OptionsButton onClick={this.props.onClose}>Save & Exit</OptionsButton> </div> </div> } })
  75. Slide № 188 diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx index a2e1d2e..2333c9f 100644

    --- src/app/ui/OptionsPlayer.jsx +++ src/app/ui/OptionsPlayer.jsx @@ -1,176 +1,191 @@ const enhance = compose( connect((state) => ({ options: state.options, scratch: Options.scratchPosition(state.options), })), connectIO({ onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }), onSetScratch: () => (position) => OptionsIO.setScratch(position), onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed), onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime), onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover), onToggleBackgroundAnimationsEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'system.bga.enabled': Options.toggleOption(options['system.bga.enabled']) }) ), onToggleAutoVelocityEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity']) }) + ), + onToggleGauge: ({ options }) => () => ( + OptionsIO.setOptions({ + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge']) + }) ) }), pure ) export const OptionsPlayer = React.createClass({ propTypes: { options: React.PropTypes.object, scratch: React.PropTypes.string, onClose: React.PropTypes.func, onSetPanel: React.PropTypes.func, onSetScratch: React.PropTypes.func, onSetSpeed: React.PropTypes.func, onSetLaneCover: React.PropTypes.func, onToggleBackgroundAnimationsEnabled: React.PropTypes.func, onToggleAutoVelocityEnabled: React.PropTypes.func, + onToggleGauge: React.PropTypes.func, onSetLeadTime: React.PropTypes.func }, render () { return <div className="OptionsPlayer"> <OptionsPlayer.Row label="Speed" hidden={Options.isAutoVelocityEnabled(this.props.options)} > <OptionsSpeed value={this.props.options['player.P1.speed']} onChange={this.props.onSetSpeed} /> <div className="OptionsPlayerͷhelp"> You can also change the speed in-game<br />using the Up and Down arrow keys. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="LeadTime" hidden={!Options.isAutoVelocityEnabled(this.props.options)} > <OptionsLeadTimeInputField value={Options.leadTime(this.props.options)} onChange={this.props.onSetLeadTime} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp"> Speed will be automatically adjusted<br />to maintain a consistent note velocity. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="Scratch"> <OptionsPlayerSelector type="scratch" options={SCRATCH_OPTIONS} onSelect={this.props.onSetScratch} value={this.props.scratch} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Panel"> <OptionsPlayerSelector type="panel" options={PANEL_OPTIONS} onSelect={this.props.onSetPanel} value={this.props.options['player.P1.panel']} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Cover"> <OptionsLaneCoverInputField value={Options.laneCover(this.props.options)} onChange={this.props.onSetLaneCover} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp" title="Can be negative, in this case the play area is pulled up." > The amount of play area to hide from the top. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="BGA"> <OptionsCheckbox checked={Options.isBackgroundAnimationsEnabled(this.props.options)} onToggle={this.props.onToggleBackgroundAnimationsEnabled} > Enable background animations <span className="OptionsPlayerͷhint">(720p, alpha)</span> </OptionsCheckbox> </OptionsPlayer.Row> <OptionsPlayer.Row label="AutoVel"> <OptionsCheckbox checked={Options.isAutoVelocityEnabled(this.props.options)} onToggle={this.props.onToggleAutoVelocityEnabled} > Maintain absolute note velocity <span className="OptionsPlayerͷhint">(advanced)</span> </OptionsCheckbox> </OptionsPlayer.Row> <div className="OptionsPlayerͷbuttons"> <OptionsButton onClick={this.props.onClose}>Save & Exit</OptionsButton> </div> </div> } })
  76. Slide № 189 diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx index a2e1d2e..2333c9f 100644

    --- src/app/ui/OptionsPlayer.jsx +++ src/app/ui/OptionsPlayer.jsx @@ -1,176 +1,191 @@ const enhance = compose( connect((state) => ({ options: state.options, scratch: Options.scratchPosition(state.options), })), connectIO({ onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }), onSetScratch: () => (position) => OptionsIO.setScratch(position), onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed), onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime), onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover), onToggleBackgroundAnimationsEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'system.bga.enabled': Options.toggleOption(options['system.bga.enabled']) }) ), onToggleAutoVelocityEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity']) }) + ), + onToggleGauge: ({ options }) => () => ( + OptionsIO.setOptions({ + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge']) + }) ) }), pure ) export const OptionsPlayer = React.createClass({ propTypes: { options: React.PropTypes.object, scratch: React.PropTypes.string, onClose: React.PropTypes.func, onSetPanel: React.PropTypes.func, onSetScratch: React.PropTypes.func, onSetSpeed: React.PropTypes.func, onSetLaneCover: React.PropTypes.func, onToggleBackgroundAnimationsEnabled: React.PropTypes.func, onToggleAutoVelocityEnabled: React.PropTypes.func, + onToggleGauge: React.PropTypes.func, onSetLeadTime: React.PropTypes.func }, render () { return <div className="OptionsPlayer"> <OptionsPlayer.Row label="Speed" hidden={Options.isAutoVelocityEnabled(this.props.options)} > <OptionsSpeed value={this.props.options['player.P1.speed']} onChange={this.props.onSetSpeed} /> <div className="OptionsPlayerͷhelp"> You can also change the speed in-game<br />using the Up and Down arrow keys. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="LeadTime" hidden={!Options.isAutoVelocityEnabled(this.props.options)} > <OptionsLeadTimeInputField value={Options.leadTime(this.props.options)} onChange={this.props.onSetLeadTime} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp"> Speed will be automatically adjusted<br />to maintain a consistent note velocity. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="Scratch"> <OptionsPlayerSelector type="scratch" options={SCRATCH_OPTIONS} onSelect={this.props.onSetScratch} value={this.props.scratch} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Panel"> <OptionsPlayerSelector type="panel" options={PANEL_OPTIONS} onSelect={this.props.onSetPanel} value={this.props.options['player.P1.panel']} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Cover"> <OptionsLaneCoverInputField value={Options.laneCover(this.props.options)} onChange={this.props.onSetLaneCover} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp" title="Can be negative, in this case the play area is pulled up." > The amount of play area to hide from the top. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="BGA"> <OptionsCheckbox checked={Options.isBackgroundAnimationsEnabled(this.props.options)} onToggle={this.props.onToggleBackgroundAnimationsEnabled} > Enable background animations <span className="OptionsPlayerͷhint">(720p, alpha)</span> </OptionsCheckbox> </OptionsPlayer.Row> <OptionsPlayer.Row label="AutoVel"> <OptionsCheckbox checked={Options.isAutoVelocityEnabled(this.props.options)} onToggle={this.props.onToggleAutoVelocityEnabled} > Maintain absolute note velocity <span className="OptionsPlayerͷhint">(advanced)</span> </OptionsCheckbox> </OptionsPlayer.Row> <div className="OptionsPlayerͷbuttons"> <OptionsButton onClick={this.props.onClose}>Save & Exit</OptionsButton> </div> </div> } })
  77. Slide № 190 diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx index a2e1d2e..2333c9f 100644

    --- src/app/ui/OptionsPlayer.jsx +++ src/app/ui/OptionsPlayer.jsx @@ -1,176 +1,191 @@ const enhance = compose( connect((state) => ({ options: state.options, scratch: Options.scratchPosition(state.options), })), connectIO({ onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }), onSetScratch: () => (position) => OptionsIO.setScratch(position), onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed), onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime), onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover), onToggleBackgroundAnimationsEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'system.bga.enabled': Options.toggleOption(options['system.bga.enabled']) }) ), onToggleAutoVelocityEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity']) }) + ), + onToggleGauge: ({ options }) => () => ( + OptionsIO.setOptions({ + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge']) + }) ) }), pure ) export const OptionsPlayer = React.createClass({ propTypes: { options: React.PropTypes.object, scratch: React.PropTypes.string, onClose: React.PropTypes.func, onSetPanel: React.PropTypes.func, onSetScratch: React.PropTypes.func, onSetSpeed: React.PropTypes.func, onSetLaneCover: React.PropTypes.func, onToggleBackgroundAnimationsEnabled: React.PropTypes.func, onToggleAutoVelocityEnabled: React.PropTypes.func, + onToggleGauge: React.PropTypes.func, onSetLeadTime: React.PropTypes.func }, render () { return <div className="OptionsPlayer"> <OptionsPlayer.Row label="Speed" hidden={Options.isAutoVelocityEnabled(this.props.options)} > <OptionsSpeed value={this.props.options['player.P1.speed']} onChange={this.props.onSetSpeed} /> <div className="OptionsPlayerͷhelp"> You can also change the speed in-game<br />using the Up and Down arrow keys. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="LeadTime" hidden={!Options.isAutoVelocityEnabled(this.props.options)} > <OptionsLeadTimeInputField value={Options.leadTime(this.props.options)} onChange={this.props.onSetLeadTime} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp"> Speed will be automatically adjusted<br />to maintain a consistent note velocity. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="Scratch"> <OptionsPlayerSelector type="scratch" options={SCRATCH_OPTIONS} onSelect={this.props.onSetScratch} value={this.props.scratch} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Panel"> <OptionsPlayerSelector type="panel" options={PANEL_OPTIONS} onSelect={this.props.onSetPanel} value={this.props.options['player.P1.panel']} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Cover"> <OptionsLaneCoverInputField value={Options.laneCover(this.props.options)} onChange={this.props.onSetLaneCover} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp" title="Can be negative, in this case the play area is pulled up." > The amount of play area to hide from the top. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="BGA"> <OptionsCheckbox checked={Options.isBackgroundAnimationsEnabled(this.props.options)} onToggle={this.props.onToggleBackgroundAnimationsEnabled} > Enable background animations <span className="OptionsPlayerͷhint">(720p, alpha)</span> </OptionsCheckbox> </OptionsPlayer.Row> <OptionsPlayer.Row label="AutoVel"> <OptionsCheckbox checked={Options.isAutoVelocityEnabled(this.props.options)} onToggle={this.props.onToggleAutoVelocityEnabled} > Maintain absolute note velocity <span className="OptionsPlayerͷhint">(advanced)</span> </OptionsCheckbox> </OptionsPlayer.Row> + <OptionsPlayer.Row label="Gauge"> + <OptionsCheckbox + checked={Options.isGaugeEnabled(this.props.options)} + onToggle={this.props.onToggleGauge} + > + Show expert gauge <span className="OptionsPlayerͷhint">(experimental)</span> + </OptionsCheckbox> + </OptionsPlayer.Row> + <div className="OptionsPlayerͷbuttons"> <OptionsButton onClick={this.props.onClose}>Save & Exit</OptionsButton> </div> </div> } })
  78. Slide № 191 diff --git src/app/ui/OptionsPlayer.jsx src/app/ui/OptionsPlayer.jsx index a2e1d2e..2333c9f 100644

    --- src/app/ui/OptionsPlayer.jsx +++ src/app/ui/OptionsPlayer.jsx @@ -1,176 +1,191 @@ const enhance = compose( connect((state) => ({ options: state.options, scratch: Options.scratchPosition(state.options), })), connectIO({ onSetPanel: () => (value) => OptionsIO.setOptions({ 'player.P1.panel': value }), onSetScratch: () => (position) => OptionsIO.setScratch(position), onSetSpeed: () => (speed) => OptionsIO.setSpeed(speed), onSetLeadTime: () => (leadTime) => OptionsIO.setLeadTime(leadTime), onSetLaneCover: () => (laneCover) => OptionsIO.setLaneCover(laneCover), onToggleBackgroundAnimationsEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'system.bga.enabled': Options.toggleOption(options['system.bga.enabled']) }) ), onToggleAutoVelocityEnabled: ({ options }) => () => ( OptionsIO.setOptions({ 'player.P1.auto-velocity': Options.toggleOption(options['player.P1.auto-velocity']) }) + ), + onToggleGauge: ({ options }) => () => ( + OptionsIO.setOptions({ + 'player.P1.gauge': Options.toggleGauge(options['player.P1.gauge']) + }) ) }), pure ) export const OptionsPlayer = React.createClass({ propTypes: { options: React.PropTypes.object, scratch: React.PropTypes.string, onClose: React.PropTypes.func, onSetPanel: React.PropTypes.func, onSetScratch: React.PropTypes.func, onSetSpeed: React.PropTypes.func, onSetLaneCover: React.PropTypes.func, onToggleBackgroundAnimationsEnabled: React.PropTypes.func, onToggleAutoVelocityEnabled: React.PropTypes.func, + onToggleGauge: React.PropTypes.func, onSetLeadTime: React.PropTypes.func }, render () { return <div className="OptionsPlayer"> <OptionsPlayer.Row label="Speed" hidden={Options.isAutoVelocityEnabled(this.props.options)} > <OptionsSpeed value={this.props.options['player.P1.speed']} onChange={this.props.onSetSpeed} /> <div className="OptionsPlayerͷhelp"> You can also change the speed in-game<br />using the Up and Down arrow keys. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="LeadTime" hidden={!Options.isAutoVelocityEnabled(this.props.options)} > <OptionsLeadTimeInputField value={Options.leadTime(this.props.options)} onChange={this.props.onSetLeadTime} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp"> Speed will be automatically adjusted<br />to maintain a consistent note velocity. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="Scratch"> <OptionsPlayerSelector type="scratch" options={SCRATCH_OPTIONS} onSelect={this.props.onSetScratch} value={this.props.scratch} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Panel"> <OptionsPlayerSelector type="panel" options={PANEL_OPTIONS} onSelect={this.props.onSetPanel} value={this.props.options['player.P1.panel']} /> </OptionsPlayer.Row> <OptionsPlayer.Row label="Cover"> <OptionsLaneCoverInputField value={Options.laneCover(this.props.options)} onChange={this.props.onSetLaneCover} style={{ width: '5em' }} /> <div className="OptionsPlayerͷhelp" title="Can be negative, in this case the play area is pulled up." > The amount of play area to hide from the top. </div> </OptionsPlayer.Row> <OptionsPlayer.Row label="BGA"> <OptionsCheckbox checked={Options.isBackgroundAnimationsEnabled(this.props.options)} onToggle={this.props.onToggleBackgroundAnimationsEnabled} > Enable background animations <span className="OptionsPlayerͷhint">(720p, alpha)</span> </OptionsCheckbox> </OptionsPlayer.Row> <OptionsPlayer.Row label="AutoVel"> <OptionsCheckbox checked={Options.isAutoVelocityEnabled(this.props.options)} onToggle={this.props.onToggleAutoVelocityEnabled} > Maintain absolute note velocity <span className="OptionsPlayerͷhint">(advanced)</span> </OptionsCheckbox> </OptionsPlayer.Row> + <OptionsPlayer.Row label="Gauge"> + <OptionsCheckbox + checked={Options.isGaugeEnabled(this.props.options)} + onToggle={this.props.onToggleGauge} + > + Show expert gauge <span className="OptionsPlayerͷhint">(experimental)</span> + </OptionsCheckbox> + </OptionsPlayer.Row> + <div className="OptionsPlayerͷbuttons"> <OptionsButton onClick={this.props.onClose}>Save & Exit</OptionsButton> </div> </div> } })
  79. Slide № 192 Cohesion loss Symptom: Making a simple change

    requires changing code that is further apart.
  80. Slide № 193 Cohesion loss Symptom: Reviewing a PR requires

    a lot of scrolling up and down to understand.
  81. Slide № 198 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func
  82. Slide № 199 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func Remove stuff not directly related to Navigation
  83. Slide № 200 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func
  84. Slide № 201 const enhance = connect( state => ({

    currentUser: selectCurrentUser(state), newNotificationCount: selectNewNotificationCount(state), newMessageCount: selectNewMessageCount(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout, onViewMessages: MessagesIO.viewMessages } ) class Navigation extends Component { static propTypes = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func
  85. Slide № 202 class Navigation extends Component { static propTypes

    = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem>
  86. Slide № 203 class Navigation extends Component { static propTypes

    = { currentUser: PropTypes.object, onLogin: PropTypes.func, onLogout: PropTypes.func, newNotificationCount: PropTypes.number, newMessageCount: PropTypes.number, onViewMessages: PropTypes.func } render () { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem>
  87. Slide № 204 class Navigation extends Component { render ()

    { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) </NavItem> } {loggedIn && <NavItem href='/inbox' onClick={this.props.onViewMessages}>
  88. Slide № 205 class Navigation extends Component { render ()

    { const loggedIn = !!this.props.currentUser return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> {loggedIn && <NavItem href='/notifications'> Notifications ({this.props.newNotificationCount}) </NavItem> } {loggedIn && <NavItem href='/inbox' onClick={this.props.onViewMessages}>
  89. Slide № 206 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> <NotificationsNavItem /> <MessagesNavItem />
  90. Slide № 207 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <LoginLogoutNavItem /> </Nav> </Navbar> )
  91. Slide № 208 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <LoginLogoutNavItem /> </Nav> </Navbar> )
  92. Slide № 211 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), )
  93. Slide № 212 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )
  94. Slide № 213 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  95. Slide № 214 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  96. Slide № 215 User Navigation Navigation const LoginLogoutNavItem = connect(

    state => ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  97. Slide № 216 User Navigation Navigation const LoginLogoutNavItem = connect(

    state => ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  98. Slide № 217 User Navigation Navigation const LoginLogoutNavItem = connect(

    state => ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  99. Slide № 218 User Navigation Navigation const LoginLogoutNavItem = connect(

    state => ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  100. Slide № 219 User Navigation Navigation const LoginLogoutNavItem = connect(

    state => ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) }) Delegate rendering logic
  101. Slide № 220 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( <NavItem href='#' onClick={props.onLogout}>Logout</NavItem> ) : ( <NavItem href='#' onClick={props.onLogin}>Login</NavItem> ) ) })
  102. Slide № 221 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) })
  103. Slide № 222 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) })
  104. Slide № 223 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) })
  105. Slide № 224 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) LoginLogoutNavItem.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  106. Slide № 225 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) LoginLogoutNavItem.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  107. Slide № 226 const LoginLogoutNavItem = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) LoginLogoutNavItem.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired } User Only concerned about current user and authentication Not concerned about how to render it
  108. Slide № 227 User const LoginLogoutNavItem = connect( state =>

    ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function LoginLogoutNavItem (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) LoginLogoutNavItem.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  109. Slide № 228 User const Auth = connect( state =>

    ({ currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function Auth (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  110. Slide № 229 const Auth = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function Auth (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  111. Slide № 230 const Auth = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function Auth (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired } “Render props”
  112. Slide № 232 class Navigation extends Component { render ()

    { return ( <Navbar> <Navbar.Header> <Navbar.Brand> <a href='#'>My app</a> </Navbar.Brand> </Navbar.Header> <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem> <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <LoginLogoutNavItem /> </Nav> </Navbar> )
  113. Slide № 233 <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem>

    <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <LoginLogoutNavItem /> </Nav> </Navbar> ) } }
  114. Slide № 234 <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem>

    <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <LoginLogoutNavItem /> </Nav> </Navbar> ) } }
  115. Slide № 235 <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem>

    <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <Auth /> </Nav> </Navbar> ) } } Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  116. Slide № 236 <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem>

    <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <Auth renderLoggedOut={ } renderLoggedIn={ } /> </Nav> </Navbar> ) } } Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  117. Slide № 237 <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem>

    <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <Auth renderLoggedOut={(login) => <NavItem href='#' onClick={login}>Login</NavItem> } renderLoggedIn={(user, logout) => <NavItem href='#' onClick={logout}>Logout</NavItem> } /> </Nav> </Navbar> ) } } Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  118. Slide № 238 <Nav> <NavItem href='/'>Home</NavItem> <NavItem href='/explore'>Explore</NavItem> <NavItem href='/search'>Search</NavItem>

    <NotificationsNavItem /> <MessagesNavItem /> </Nav> <Nav pullRight> <Auth renderLoggedOut={(login) => <NavItem href='#' onClick={login}>Login</NavItem> } renderLoggedIn={(user, logout) => <NavItem href='#' onClick={logout}>Logout</NavItem> } /> </Nav> </Navbar> ) } } Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  119. Slide № 240 const Auth = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function Auth (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired }
  120. Slide № 241 const Auth = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function Auth (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired } Objective: Connect UI to data source.
  121. Slide № 242 const Auth = connect( state => ({

    currentUser: selectCurrentUser(state) }), { onLogin: LoginIO.showLoginModal, onLogout: LoginIO.logout } )(function Auth (props) { return ( props.currentUser ? ( props.renderLoggedIn(props.currentUser, props.onLogout) ) : ( props.renderLoggedOut(props.onLogin) ) ) }) Auth.propTypes = { renderLoggedIn: PropTypes.func.isRequired, renderLoggedOut: PropTypes.func.isRequired } “Data connector component”
  122. Slide № 251 I don’t know of any way to

    detect these smells automatically.
  123. Slide № 255 Not all smells are bad. Example: Duplicated

    code can sometimes be easier to read