Mailspring/src/component-registry.coffee
Juan Tejada cfdc401c54 update(extensions): Rename DraftStoreExtension and MessageStoreExtension
Summary:
- Rename DraftStoreExtension to ComposerExtension
- Rename MessageStoreExtension to MessageViewExtension
- Rename ContenteditablePlugin to ContenteditableExtension
  - Update Contenteditable to use new naming convention
  - Adds support for extension handlers as props
- Add ExtensionRegistry to register extensions:
  - ContenteditableExtensions will not be registered through the
    ExtensionRegistry. They are meant for internal use, or if anyone wants
    to use our Contenteditable component directly in their plugins.
  - Adds specs
- Refactors internal_packages and src to use new names and new ExtensionRegistry api
- Adds deprecation util function and deprecation notices for old api methods:
  - DraftStore.{registerExtension, unregisterExtension}
  - MessageStore.{registerExtension, unregisterExtension}
  - DraftStoreExtension.{onMouseUp, onTabDown}
  - MessageStoreExtension
- Adds and updates docs

Test Plan: - Unit tests

Reviewers: bengotow, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D2293
2015-11-30 16:08:05 -08:00

169 lines
5.8 KiB
CoffeeScript

_ = require 'underscore'
{Listener, Publisher} = require './flux/modules/reflux-coffee'
CoffeeHelpers = require './flux/coffee-helpers'
###
Public: The ComponentRegistry maintains an index of React components registered
by Nylas packages. Components can use {InjectedComponent} and {InjectedComponentSet}
to dynamically render components registered with the ComponentRegistry.
Section: Stores
###
class ComponentRegistry
@include: CoffeeHelpers.includeModule
@include Publisher
@include Listener
constructor: ->
@_registry = {}
@_showComponentRegions = false
# Public: Register a new component with the Component Registry.
# Typically, packages call this method from their main `activate` method
# to extend the Nylas user interface, and call the corresponding `unregister`
# method in `deactivate`.
#
# * `component` {Object} A React Component with a `displayName`
# * `options` {Object}:
#
# * `role`: (optional) {String} If you want to display your component in a location
# desigated by a role, pass the role identifier.
#
# * `modes`: (optional) {Array} If your component should only be displayed
# in particular Workspace Modes, pass an array of supported modes.
# ('list', 'split', etc.)
#
# * `location`: (optional) {Object} If your component should be displayed in a
# column or toolbar, pass the fully qualified location object, such as:
# `WorkspaceStore.Location.ThreadList`
#
# Note that for advanced use cases, you can also pass (`modes`, `roles`, `locations`)
# with arrays instead of single values.
#
# This method is chainable.
#
register: (component, options) =>
if component.view?
return console.warn("Ignoring component trying to register with old CommandRegistry.register syntax")
throw new Error("ComponentRegistry.register() requires `options` that describe the component") unless options
throw new Error("ComponentRegistry.register() requires `component`, a React component") unless component
throw new Error("ComponentRegistry.register() requires that your React Component defines a `displayName`") unless component.displayName
{locations, modes, roles} = @_pluralizeDescriptor(options)
throw new Error("ComponentRegistry.register() requires `role` or `location`") if not roles and not locations
if @_registry[component.displayName] and @_registry[component.displayName].component isnt component
throw new Error("ComponentRegistry.register(): A different component was already registered with the name #{component.displayName}")
@_registry[component.displayName] = {component, locations, modes, roles}
# Trigger listeners. It's very important the component registry is debounced.
# During app launch packages register tons of components and if we re-rendered
# the entire UI after each registration it takes forever to load the UI.
@triggerDebounced()
# Return `this` for chaining
@
unregister: (component) =>
if _.isString(component)
throw new Error("ComponentRegistry.unregister() must be called with a component.")
delete @_registry[component.displayName]
@triggerDebounced()
# Public: Retrieve the registry entry for a given name.
#
# - `name`: The {String} name of the registered component to retrieve.
#
# Returns a {React.Component}
#
findComponentByName: (name) =>
@_registry[name]?.component
###
Public: Retrieve all of the registry entries matching a given descriptor.
```coffee
ComponentRegistry.findComponentsMatching({
role: 'Composer:ActionButton'
})
ComponentRegistry.findComponentsMatching({
location: WorkspaceStore.Location.RootSidebar.Toolbar
})
```
- `descriptor`: An {Object} that specifies set of components using the
available keys below.
* `mode`: (optional) {String} Components that specifically list modes
will only be returned if they include this mode.
* `role`: (optional) {String} Only return components that have registered
for this role.
* `location`: (optional) {Object} Only return components that have registered
for this location.
Note that for advanced use cases, you can also pass (`modes`, `roles`, `locations`)
with arrays instead of single values.
Returns an {Array} of {React.Component} objects
###
findComponentsMatching: (descriptor) =>
if not descriptor?
throw new Error("ComponentRegistry.findComponentsMatching called without descriptor")
{locations, modes, roles} = @_pluralizeDescriptor(descriptor)
if not locations and not modes and not roles
throw new Error("ComponentRegistry.findComponentsMatching called with an empty descriptor")
# Made into a convenience function because default
# values (`[]`) are necessary and it was getting messy.
overlaps = (entry = [], search = []) ->
_.intersection(entry, search).length > 0
entries = _.values @_registry
entries = _.filter entries, (entry) ->
if modes and entry.modes and not overlaps(modes, entry.modes)
return false
if locations and not overlaps(locations, entry.locations)
return false
if roles and not overlaps(roles, entry.roles)
return false
return true
_.map entries, (entry) -> entry.component
triggerDebounced: _.debounce(( -> @trigger(@)), 1)
_pluralizeDescriptor: (descriptor) ->
{locations, modes, roles} = descriptor
modes = [descriptor.mode] if descriptor.mode
roles = [descriptor.role] if descriptor.role
locations = [descriptor.location] if descriptor.location
{locations, modes, roles}
_clear: =>
@_registry = {}
# Showing Component Regions
toggleComponentRegions: ->
@_showComponentRegions = !@_showComponentRegions
@trigger(@)
showComponentRegions: =>
@_showComponentRegions
module.exports = new ComponentRegistry()