Skip to content

Commit

Permalink
[MM-10017] Plugin framework: add support for plugins to override righ…
Browse files Browse the repository at this point in the history
…t-hand sidebar (mattermost#2827)

* Added support for plugins to override right-hand sidebar [MM-10017](mattermost/mattermost#10169)

* Updated and added RHS tests for plugin case

* Removed unused props for RHS Plugin component

* Changed plugin export to export RHS action. Added pluginId to Pluggable component to support showing only one when multiple of the same components are registered.

* Return the RHS action from the registry function

* Changed RHS registry to return the showRHSPlugin action with plugin id
already set. Title passed to RHS registry changed to support string or
JSX element.

* Fixed id to check for which pluggable component to show
  • Loading branch information
marianunez authored and crspeller committed Jun 20, 2019
1 parent ef540eb commit 4454b53
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 59 deletions.
10 changes: 10 additions & 0 deletions actions/views/rhs.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ export function showSearchResults() {
};
}

export function showRHSPlugin(pluginId) {
const action = {
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.PLUGIN,
pluginId,
};

return action;
}

export function showFlaggedPosts() {
return async (dispatch, getState) => {
const state = getState();
Expand Down
58 changes: 47 additions & 11 deletions components/search_results/search_results.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
import React from 'react';
import Scrollbars from 'react-custom-scrollbars';

import {FormattedMessage} from 'react-intl';

import {debounce} from 'mattermost-redux/actions/helpers';

import * as Utils from 'utils/utils.jsx';
Expand Down Expand Up @@ -58,6 +60,7 @@ export default class SearchResults extends React.PureComponent {
isMentionSearch: PropTypes.bool,
isFlaggedPosts: PropTypes.bool,
isPinnedPosts: PropTypes.bool,
isCard: PropTypes.bool,
channelDisplayName: PropTypes.string.isRequired,
dataRetentionEnableMessageDeletion: PropTypes.bool.isRequired,
dataRetentionMessageRetentionDays: PropTypes.string,
Expand Down Expand Up @@ -209,18 +212,51 @@ export default class SearchResults extends React.PureComponent {
}
}

return (
<div
className='sidebar-right__body'
id='searchResultsContainer'
>
<SearchResultsHeader
isMentionSearch={this.props.isMentionSearch}
isFlaggedPosts={this.props.isFlaggedPosts}
isPinnedPosts={this.props.isPinnedPosts}
channelDisplayName={this.props.channelDisplayName}
isLoading={this.props.isSearchingTerm}
var formattedTitle = (
<FormattedMessage
id='search_header.results'
defaultMessage='Search Results'
/>
);

if (this.props.isMentionSearch) {
formattedTitle = (
<FormattedMessage
id='search_header.title2'
defaultMessage='Recent Mentions'
/>
);
} else if (this.props.isFlaggedPosts) {
formattedTitle = (
<FormattedMessage
id='search_header.title3'
defaultMessage='Flagged Posts'
/>
);
} else if (this.props.isPinnedPosts) {
formattedTitle = (
<FormattedMessage
id='search_header.title4'
defaultMessage='Pinned posts in {channelDisplayName}'
values={{
channelDisplayName: this.props.channelDisplayName,
}}
/>
);
} else if (this.props.isCard) {
formattedTitle = (
<FormattedMessage
id='search_header.title5'
defaultMessage='Extra information'
/>
);
}

return (
<div className='sidebar-right__body'>
<SearchResultsHeader>
{formattedTitle}
</SearchResultsHeader>
<Scrollbars
ref='scrollbars'
autoHide={true}
Expand Down
48 changes: 2 additions & 46 deletions components/search_results_header/search_results_header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,14 @@ import Constants from 'utils/constants.jsx';

export default class SearchResultsHeader extends React.Component {
static propTypes = {
isMentionSearch: PropTypes.bool,
isFlaggedPosts: PropTypes.bool,
isPinnedPosts: PropTypes.bool,
isCard: PropTypes.bool,
channelDisplayName: PropTypes.string.isRequired,
children: PropTypes.element,
actions: PropTypes.shape({
closeRightHandSide: PropTypes.func,
toggleRhsExpanded: PropTypes.func.isRequired,
}),
};

render() {
var title = (
<FormattedMessage
id='search_header.results'
defaultMessage='Search Results'
/>
);

const closeSidebarTooltip = (
<Tooltip id='closeSidebarTooltip'>
<FormattedMessage
Expand Down Expand Up @@ -56,42 +45,9 @@ export default class SearchResultsHeader extends React.Component {
</Tooltip>
);

if (this.props.isMentionSearch) {
title = (
<FormattedMessage
id='search_header.title2'
defaultMessage='Recent Mentions'
/>
);
} else if (this.props.isFlaggedPosts) {
title = (
<FormattedMessage
id='search_header.title3'
defaultMessage='Flagged Posts'
/>
);
} else if (this.props.isPinnedPosts) {
title = (
<FormattedMessage
id='search_header.title4'
defaultMessage='Pinned posts in {channelDisplayName}'
values={{
channelDisplayName: this.props.channelDisplayName,
}}
/>
);
} else if (this.props.isCard) {
title = (
<FormattedMessage
id='search_header.title5'
defaultMessage='Extra information'
/>
);
}

return (
<div className='sidebar--right__header'>
<span className='sidebar--right__title'>{title}</span>
<span className='sidebar--right__title'>{this.props.children}</span>
<div className='pull-right'>
<button
type='button'
Expand Down
3 changes: 2 additions & 1 deletion components/sidebar_right/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ function mapStateToProps(state) {
currentUserId: getCurrentUserId(state),
postRightVisible: Boolean(getSelectedPostId(state)),
postCardVisible: Boolean(getSelectedPostCardId(state)),
searchVisible: Boolean(rhsState),
searchVisible: Boolean(rhsState) && rhsState !== RHSStates.PLUGIN,
previousRhsState: getPreviousRhsState(state),
isMentionSearch: rhsState === RHSStates.MENTION,
isFlaggedPosts: rhsState === RHSStates.FLAG,
isPinnedPosts: rhsState === RHSStates.PIN,
isPluginView: rhsState === RHSStates.PLUGIN,
};
}

Expand Down
11 changes: 11 additions & 0 deletions components/sidebar_right/sidebar_right.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import RhsCard from 'components/rhs_card';
import SearchBar from 'components/search_bar';
import SearchResults from 'components/search_results';

import RhsPlugin from 'plugins/rhs_plugin';

export default class SidebarRight extends React.PureComponent {
static propTypes = {
isExpanded: PropTypes.bool.isRequired,
Expand All @@ -27,6 +29,7 @@ export default class SidebarRight extends React.PureComponent {
isMentionSearch: PropTypes.bool,
isFlaggedPosts: PropTypes.bool,
isPinnedPosts: PropTypes.bool,
isPluginView: PropTypes.bool,
previousRhsState: PropTypes.string,
actions: PropTypes.shape({
setRhsExpanded: PropTypes.func.isRequired,
Expand Down Expand Up @@ -67,6 +70,7 @@ export default class SidebarRight extends React.PureComponent {
postCardVisible,
previousRhsState,
searchVisible,
isPluginView,
} = this.props;

let content = null;
Expand Down Expand Up @@ -117,6 +121,13 @@ export default class SidebarRight extends React.PureComponent {
/>
</div>
);
} else if (isPluginView) {
content = (
<div className='post-right__container'>
<div className='search-bar__container channel-header alt'>{searchForm}</div>
<RhsPlugin/>
</div>
);
} else if (postCardVisible) {
content = (
<div className='post-right__container'>
Expand Down
14 changes: 13 additions & 1 deletion plugins/pluggable/pluggable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export default class Pluggable extends React.PureComponent {
* Logged in user's theme
*/
theme: PropTypes.object.isRequired,

/*
* Id of the specific component to be plugged.
*/
pluggableId: PropTypes.string,
}

render() {
Expand All @@ -50,8 +55,15 @@ export default class Pluggable extends React.PureComponent {
props = {...props, ...childrenProps};

// Override the default component with any registered plugin's component
// Select a specific component by pluginId if available
if (components.hasOwnProperty(componentName)) {
const pluginComponents = components[componentName];
let pluginComponents = components[componentName];

if (this.props.pluggableId) {
pluginComponents = pluginComponents.filter(
(element) => element.id === this.props.pluggableId);
}

const content = pluginComponents.map((p) => {
const PluginComponent = p.component;
return (
Expand Down
26 changes: 26 additions & 0 deletions plugins/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
unregisterPluginReconnectHandler,
} from 'actions/websocket_actions.jsx';

import {showRHSPlugin} from 'actions/views/rhs';

import {
registerPluginTranslationsSource,
} from 'actions/views/root';
Expand Down Expand Up @@ -434,4 +436,28 @@ export default class PluginRegistry {
registerTranslations(getTranslationsForLocale) {
registerPluginTranslationsSource(this.id, getTranslationsForLocale);
}

// Register a Right-Hand Sidebar component by providing a title for the right hand component.
// Accepts the following:
// - title - A string or JSX element to display as a title for the RHS.
// - component - A react component to display in the Right-Hand Sidebar.
// Returns:
// - id: a unique identifier
// - showRHSPlugin: the action to dispatch that will open the RHS.
registerRightHandSidebarComponent(component, title) {
const id = generateId();

store.dispatch({
type: ActionTypes.RECEIVED_PLUGIN_COMPONENT,
name: 'RightHandSidebarComponent',
data: {
id,
pluginId: this.id,
component,
title,
},
});

return {id, showRHSPlugin: showRHSPlugin(id)};
}
}
23 changes: 23 additions & 0 deletions plugins/rhs_plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {connect} from 'react-redux';

import {getPluginId} from 'selectors/rhs';

import RHSPlugin from './rhs_plugin.jsx';

function mapStateToProps(state) {
const rhsPlugins = state.plugins.components.RightHandSidebarComponent;
const pluginId = getPluginId(state);

const pluginName = rhsPlugins.find((element) => element.id === pluginId).title;

return {
title: pluginName,
pluggableId: pluginId,
};
}

export default connect(mapStateToProps)(RHSPlugin);
37 changes: 37 additions & 0 deletions plugins/rhs_plugin/rhs_plugin.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import PropTypes from 'prop-types';
import React from 'react';

import SearchResultsHeader from 'components/search_results_header';

import Pluggable from 'plugins/pluggable';

export default class RhsPlugin extends React.PureComponent {
static propTypes = {
title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
pluggableId: PropTypes.string.isRequired,
}

render() {
return (
<div
id='rhsContainer'
className='sidebar-right__body'
ref='sidebarbody'
>
<SearchResultsHeader>
{this.props.title}
</SearchResultsHeader>
<Pluggable
pluggableName='RightHandSidebarComponent'
pluggableId={this.props.pluggableId}
/>
</div>
);
}
}
13 changes: 13 additions & 0 deletions reducers/views/rhs.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ function searchTerms(state = '', action) {
}
}

function pluginId(state = '', action) {
switch (action.type) {
case ActionTypes.UPDATE_RHS_STATE:
if (action.state === RHSStates.PLUGIN) {
return action.pluginId;
}
return '';
default:
return state;
}
}

function searchResultsTerms(state = '', action) {
switch (action.type) {
case ActionTypes.UPDATE_RHS_SEARCH_RESULTS_TERMS:
Expand Down Expand Up @@ -211,6 +223,7 @@ export default combineReducers({
rhsState,
searchTerms,
searchResultsTerms,
pluginId,
isSearchingFlaggedPost,
isSearchingPinnedPost,
isSidebarOpen,
Expand Down
Loading

0 comments on commit 4454b53

Please sign in to comment.