_ = 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()