strip out sample bits (moved to demo repository)

This commit is contained in:
Jesse Hallam 2018-07-24 16:02:46 -04:00
parent 7641f7cf17
commit daaf7822ea
No known key found for this signature in database
GPG key ID: E7959EB6518AF966
36 changed files with 12 additions and 1239 deletions

View file

@ -1,105 +0,0 @@
# Sample Plugin: Web App
The web app component of this sample plugin is written in Javascript, and leverages [React](https://reactjs.org/) and [Redux](https://redux.js.org/). It registers a component type for all of the supported registration calls, parses a custom webhook to detect when the server plugin's hooks status changes, and pings the server on network reconnect to synchronize state.
Each of the included files or folders is outlined below.
## [package.json](package.json)
See the NPM documentation on [package.json](https://docs.npmjs.com/files/package.json). It defines a `build` script to invoke webpack and generate a bundle, a `lint` script to run the `src/` directory through the [eslint](https://eslint.org/) checker, and a `fix` script that both lints and automatically tries to fix issues.
## [package-lock.json](package-lock.json)
See the NPM documentation on [package-lock.json](https://docs.npmjs.com/files/package-lock.json).
## [webpack.config.js](webpack.config.js)
See the Webpack documentation on [configuration](https://webpack.js.org/configuration/). Notably, this configuration specifies external dependencies on React, Redux and React Redux to avoid bundling these libraries and duplicating the versions already part of the Mattermost Web App.
## [.eslintrc.json](.eslintrc.json)
This defines rules to configure [eslint](https://eslint.org/) as part of invoking the `lint` and `fix` scripts. The styles are based on the rules used by the Mattermost Webapp.
## [node\_modules](node_modules)
This is the [location](https://docs.npmjs.com/files/folders#node-modules) in which [npm](https://www.npmjs.com/) installs any necessary Javascript dependencies.
## [src/index.js](src/index.js)
This is the entry point of the web app. When the plugin is loaded, this file is executed, registering the plugin with the Mattermost Webapp.
## [src/plugin\_id.js](src/plugin_id.js)
This is a file generated by the [build/manifest](../build/manifest) tool that captures the plugin id from [plugin.json](../plugin.json). It simplifies the need to hard-code the plugin id in multiple places by exporting a constant for use instead.
## [src/plugin.jsx](src/plugin.jsx)
This defines the Plugin class requires by the Mattermost Webapp, registering all the components and callbacks used by the plugin on `initialize` and logging a console message on `uninitialize`.
## [src/reducer.js](src/reducer.js)
This exports a [reducer](https://redux.js.org/basics/reducers) tracking the plugin hook's status. It is part of the global state of the Mattermost Webapp, and accessible at `store['plugins' + PluginId]`.
## [src/selectors.js](src/selectors.js)
This defines selectors into the Redux state managed by the plugin to determine if the plugin is enabled or disabled.
## [src/action\_types.js](src/action_types.js)
This exports constants used by the Redux [actions](https://redux.js.org/basics/actions) in [action\_types.js](src/action_types.js). It's important to namespace any action types to avoid unintentional collisions with action types from the Mattermost Webapp or other plugins.
## [src/actions.js](src/actions.js)
This exports Redux [actions](https://redux.js.org/basics/actions) for triggering the root component, as well as querying the server for the current plugin hooks status and responding to websocket events emitted by the server for the plugin.
## [components](components)
This folder exports a number of components illustrating plugin functionality.
## Root
This plugin registers a modal-like root component that displays above all other components, and is triggered by interacting with other plugin components on the page:
![root](docs/root.png)
## User Attributes
This plugin registers a user attributes components displaying a static string:
![user attributes](docs/user_attributes.png)
## User Actions
This plugin registers a user actions components displaying a static string followed by a simple `<button>` that triggers the root component:
![user actions](docs/user_actions.png)
## Left Sidebar Header
This plugin registers a left sidebar header component displaying the current status of the plugin hooks:
![left sidebar header](docs/left_sidebar_header.png)
## Bottom Team Sidebar
This plugin registers a bottom team sidebar component displaying a plugin icon:
![bottom team sidebar](docs/bottom_team_sidebar.png)
## Channel Header Button Action
This plugin registers a channel header button action displaying a plugin icon that, when clicked, triggers the root component:
![channel header button](docs/channel_header_button.png)
## Main Menu Action
This plugin registers a main menu action that, when clicked, triggers the root component:
![main menu](docs/main_menu.png)
# Post Type
This plugin renders a custom post type as part of handling the `OnActivate` hook in the server, dumping the current plugin configuration:
![post type](docs/post_type.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,7 +1,7 @@
{
"name": "webapp",
"version": "0.0.1",
"description": "This plugin serves as a reference guide for best practices and build scripts when writing Mattermost plugins.",
"description": "This plugin serves as a starting point for writing a Mattermost plugin.",
"main": "src/index.js",
"scripts": {
"build": "webpack --mode=production",
@ -9,7 +9,7 @@
"fix": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx . --quiet --fix"
},
"author": "",
"license": "ISC",
"license": "",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",

View file

@ -1,7 +0,0 @@
import PluginId from './plugin_id';
// Namespace your actions to avoid collisions.
export const STATUS_CHANGE = PluginId + '_status_change';
export const OPEN_ROOT_MODAL = PluginId + '_open_root_modal';
export const CLOSE_ROOT_MODAL = PluginId + '_close_root_modal';

View file

@ -1,31 +0,0 @@
import PluginId from './plugin_id';
import {STATUS_CHANGE, OPEN_ROOT_MODAL, CLOSE_ROOT_MODAL} from './action_types';
export const openRootModal = () => (dispatch) => {
dispatch({
type: OPEN_ROOT_MODAL,
});
};
export const closeRootModal = () => (dispatch) => {
dispatch({
type: CLOSE_ROOT_MODAL,
});
};
export const mainMenuAction = openRootModal;
export const channelHeaderButtonAction = openRootModal;
export const getStatus = () => (dispatch) => {
fetch('/plugins/' + PluginId + '/').then((r) => r.json()).then((r) => {
dispatch({
type: STATUS_CHANGE,
data: r.enabled,
});
});
};
export const websocketStatusChange = (message) => (dispatch) => dispatch({
type: STATUS_CHANGE,
data: message.data.enabled,
});

View file

@ -1,10 +0,0 @@
import React from 'react';
const BottomTeamSidebarComponent = () => (
<i
className='icon fa fa-plug'
style={{color: 'white'}}
/>
);
export default BottomTeamSidebarComponent;

View file

@ -1,9 +0,0 @@
import React from 'react';
export const MainMenuMobileIcon = () => (
<i className='icon fa fa-plug'/>
);
export const ChannelHeaderButtonIcon = () => (
<i className='icon fa fa-plug'/>
);

View file

@ -1,11 +0,0 @@
import {connect} from 'react-redux';
import {isEnabled} from 'selectors';
import LeftSidebarHeader from './left_sidebar_header';
const mapStateToProps = (state) => ({
enabled: isEnabled(state),
});
export default connect(mapStateToProps)(LeftSidebarHeader);

View file

@ -1,32 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
// LeftSidebarHeader is a pure component, later connected to the Redux store so as to
// show the plugin's enabled / disabled status.
export default class LeftSidebarHeader extends React.PureComponent {
static propTypes = {
enabled: PropTypes.bool.isRequired,
}
render() {
const iconStyle = {
display: 'inline-block',
margin: '0 7px 0 1px',
};
const style = {
margin: '.5em 0 .5em',
padding: '0 12px 0 15px',
color: 'rgba(255,255,255,0.6)',
};
return (
<div style={style}>
<i
className='icon fa fa-plug'
style={iconStyle}
/>
{'Sample Plugin: '} {this.props.enabled ? <span>{ 'Enabled' }</span> : <span>{ 'Disabled' }</span>}
</div>
);
}
}

View file

@ -1,36 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const {formatText, messageHtmlToComponent} = window.PostUtils;
export default class PostType extends React.PureComponent {
static propTypes = {
post: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
}
render() {
const style = getStyle(this.props.theme);
const post = {...this.props.post};
const message = post.message || '';
const formattedText = messageHtmlToComponent(formatText(message));
return (
<div>
{formattedText}
<pre style={style.configuration}>
{JSON.stringify(post.props, null, 4)}
</pre>
</div>
);
}
}
const getStyle = (theme) => ({
configuration: {
padding: '1em',
color: theme.centerChannelBg,
backgroundColor: theme.centerChannelColor,
},
});

View file

@ -1,17 +0,0 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {closeRootModal} from 'actions';
import {isRootModalVisible} from 'selectors';
import Root from './root';
const mapStateToProps = (state) => ({
visible: isRootModalVisible(state),
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
close: closeRootModal,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Root);

View file

@ -1,54 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const Root = ({visible, close, theme}) => {
if (!visible) {
return null;
}
const style = getStyle(theme);
return (
<div
style={style.backdrop}
onClick={close}
>
<div style={style.modal}>
{ 'You have triggered the root component of the sample plugin.' }
<br/>
<br/>
{ 'Click anywhere to close.' }
</div>
</div>
);
};
Root.propTypes = {
visible: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
const getStyle = (theme) => ({
backdrop: {
position: 'absolute',
display: 'flex',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.50)',
zIndex: 2000,
alignItems: 'center',
justifyContent: 'center',
},
modal: {
height: '250px',
width: '400px',
padding: '1em',
color: theme.centerChannelColor,
backgroundColor: theme.centerChannelBg,
},
});
export default Root;

View file

@ -1,12 +0,0 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {openRootModal} from 'actions';
import UserActions from './user_actions';
const mapDispatchToProps = (dispatch) => bindActionCreators({
openRootModal,
}, dispatch);
export default connect(null, mapDispatchToProps)(UserActions);

View file

@ -1,36 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class UserActionsComponent extends React.PureComponent {
static propTypes = {
openRootModal: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
}
onClick = () => {
this.props.openRootModal();
}
render() {
const style = getStyle(this.props.theme);
return (
<div>
{ 'Sample Plugin: '}
<button
style={style.button}
onClick={this.onClick}
>
{'Action'}
</button>
</div>
);
}
}
const getStyle = (theme) => ({
button: {
color: theme.buttonColor,
backgroundColor: theme.buttonBg,
},
});

View file

@ -1,10 +0,0 @@
import React from 'react';
export default class UserAttributes extends React.PureComponent {
render() {
return (
<div>{'Sample Plugin: User Attributes'}</div>
);
}
}

View file

@ -1,4 +1,10 @@
import Plugin from './plugin';
import PluginId from './plugin_id';
export default class Plugin {
// eslint-disable-next-line no-unused-vars
initialize(registry, store) {
// @see https://developers.mattermost.com/extend/plugins/webapp/reference/
}
}
window.registerPlugin(PluginId, new Plugin());

View file

@ -1,69 +0,0 @@
import React from 'react';
import PluginId from './plugin_id';
import Root from './components/root';
import BottomTeamSidebar from './components/bottom_team_sidebar';
import LeftSidebarHeader from './components/left_sidebar_header';
import UserAttributes from './components/user_attributes';
import UserActions from './components/user_actions';
import PostType from './components/post_type';
import {
MainMenuMobileIcon,
ChannelHeaderButtonIcon,
} from './components/icons';
import {
mainMenuAction,
channelHeaderButtonAction,
websocketStatusChange,
getStatus,
} from './actions';
import reducer from './reducer';
export default class SamplePlugin {
initialize(registry, store) {
registry.registerRootComponent(Root);
registry.registerPopoverUserAttributesComponent(UserAttributes);
registry.registerPopoverUserActionsComponent(UserActions);
registry.registerLeftSidebarHeaderComponent(LeftSidebarHeader);
registry.registerBottomTeamSidebarComponent(
BottomTeamSidebar,
);
registry.registerChannelHeaderButtonAction(
<ChannelHeaderButtonIcon/>,
() => store.dispatch(channelHeaderButtonAction()),
'Sample Plugin',
);
registry.registerPostTypeComponent('custom_sample_plugin', PostType);
registry.registerMainMenuAction(
'Sample Plugin',
() => store.dispatch(mainMenuAction()),
<MainMenuMobileIcon/>,
);
registry.registerWebSocketEventHandler(
'custom_' + PluginId + '_status_change',
(message) => {
store.dispatch(websocketStatusChange(message));
},
);
registry.registerReducer(reducer);
// Immediately fetch the current plugin status.
store.dispatch(getStatus());
// Fetch the current status whenever we recover an internet connection.
registry.registerReconnectHandler(() => {
store.dispatch(getStatus());
});
}
uninitialize() {
//eslint-disable-next-line no-console
console.log(PluginId + '::uninitialize()');
}
}

View file

@ -1,30 +0,0 @@
import {combineReducers} from 'redux';
import {STATUS_CHANGE, OPEN_ROOT_MODAL, CLOSE_ROOT_MODAL} from './action_types';
const enabled = (state = false, action) => {
switch (action.type) {
case STATUS_CHANGE:
return action.data;
default:
return state;
}
};
const rootModalVisible = (state = false, action) => {
switch (action.type) {
case OPEN_ROOT_MODAL:
return true;
case CLOSE_ROOT_MODAL:
return false;
default:
return state;
}
};
export default combineReducers({
enabled,
rootModalVisible,
});

View file

@ -1,7 +0,0 @@
import PluginId from './plugin_id';
const getPluginState = (state) => state['plugins-' + PluginId] || {};
export const isEnabled = (state) => getPluginState(state).enabled;
export const isRootModalVisible = (state) => getPluginState(state).rootModalVisible;