Files
flipper/src/PluginContainer.js
Daniel Büchele 22e3017cdc move NotificationHub from plugins to main folder
Summary: As all device plugins are now moved to the plugins folder, this was the last thing in this folder. I am moving it out and putting it next to the core UI parts of the app.

Reviewed By: jknoxville

Differential Revision: D10337838

fbshipit-source-id: 6fa699c28e5df8a53719179fbb760f2a140bafc4
2018-10-15 02:59:37 -07:00

203 lines
5.2 KiB
JavaScript

/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import type {FlipperPlugin, FlipperBasePlugin} from './plugin.js';
import type LogManager from './fb-stubs/Logger';
import type Client from './Client.js';
import type BaseDevice from './devices/BaseDevice.js';
import type {Props as PluginProps} from './plugin';
import {FlipperDevicePlugin} from './plugin.js';
import type {Notification} from './plugin.js';
import {
ErrorBoundary,
Component,
FlexColumn,
FlexRow,
colors,
styled,
} from 'flipper';
import React from 'react';
import {connect} from 'react-redux';
import {setPluginState} from './reducers/pluginStates.js';
import {setActiveNotifications} from './reducers/notifications.js';
import {devicePlugins, clientPlugins} from './plugins/index.js';
import NotificationsHub from './NotificationsHub';
import {activateMenuItems} from './MenuBar.js';
const Container = styled(FlexColumn)({
width: 0,
flexGrow: 1,
flexShrink: 1,
backgroundColor: colors.white,
});
const SidebarContainer = styled(FlexRow)({
backgroundColor: colors.light02,
height: '100%',
overflow: 'scroll',
});
type Props = {
logger: LogManager,
selectedDevice: BaseDevice,
selectedPlugin: ?string,
selectedApp: ?string,
pluginStates: {
[pluginKey: string]: Object,
},
clients: Array<Client>,
setPluginState: (payload: {
pluginKey: string,
state: Object,
}) => void,
setActiveNotifications: ({
pluginId: string,
notifications: Array<Notification>,
}) => void,
deepLinkPayload: ?string,
};
type State = {
activePlugin: ?Class<FlipperBasePlugin<>>,
target: Client | BaseDevice | null,
pluginKey: string,
};
function computeState(props: Props): State {
// plugin changed
let activePlugin = [NotificationsHub, ...devicePlugins].find(
(p: Class<FlipperDevicePlugin<>>) => p.id === props.selectedPlugin,
);
let target = props.selectedDevice;
let pluginKey = 'unknown';
if (activePlugin) {
pluginKey = `${props.selectedDevice.serial}#${activePlugin.id}`;
} else {
target = props.clients.find(
(client: Client) => client.id === props.selectedApp,
);
activePlugin = clientPlugins.find(
(p: Class<FlipperPlugin<>>) => p.id === props.selectedPlugin,
);
if (!activePlugin || !target) {
throw new Error(
`Plugin "${props.selectedPlugin || ''}" could not be found.`,
);
}
pluginKey = `${target.id}#${activePlugin.id}`;
}
return {
activePlugin,
target,
pluginKey,
};
}
class PluginContainer extends Component<Props, State> {
plugin: ?FlipperBasePlugin<>;
constructor(props: Props) {
super();
this.state = computeState(props);
}
componentWillReceiveProps(nextProps: Props) {
if (
nextProps.selectedDevice !== this.props.selectedDevice ||
nextProps.selectedApp !== this.props.selectedApp ||
nextProps.selectedPlugin !== this.props.selectedPlugin
) {
this.setState(computeState(nextProps));
}
}
refChanged = (ref: ?FlipperBasePlugin<>) => {
if (this.plugin) {
this.plugin._teardown();
this.plugin = null;
}
const {target} = this.state;
if (ref && target) {
activateMenuItems(ref);
ref._init();
this.props.logger.trackTimeSince(`activePlugin-${ref.constructor.id}`);
this.plugin = ref;
}
};
render() {
const {pluginStates, setPluginState, setActiveNotifications} = this.props;
const {activePlugin, pluginKey, target} = this.state;
if (!activePlugin || !target) {
return null;
}
const props: PluginProps<Object> = {
key: pluginKey,
logger: this.props.logger,
persistedState: pluginStates[pluginKey] || {},
setPersistedState: state => {
// We are using setTimout here to wait for previous state updated to
// finish before triggering a new state update. Otherwise this can
// cause race conditions, with multiple state updates happening at the
// same time.
setTimeout(() => setPluginState({pluginKey, state}), 0);
},
setActiveNotifications: (notifications: Array<Notification>) =>
setActiveNotifications({
pluginId: pluginKey,
notifications: notifications,
}),
target,
deepLinkPayload: this.props.deepLinkPayload,
ref: this.refChanged,
};
return (
<React.Fragment>
<Container key="plugin">
<ErrorBoundary
heading={`Plugin "${
activePlugin.title
}" encountered an error during render`}
logger={this.props.logger}>
{React.createElement(activePlugin, props)}
</ErrorBoundary>
</Container>
<SidebarContainer id="detailsSidebar" />
</React.Fragment>
);
}
}
export default connect(
({
application: {rightSidebarVisible, rightSidebarAvailable},
connections: {
selectedPlugin,
selectedDevice,
selectedApp,
clients,
deepLinkPayload,
},
pluginStates,
}) => ({
selectedPlugin,
selectedDevice,
pluginStates,
selectedApp,
clients,
deepLinkPayload,
}),
{
setPluginState,
setActiveNotifications,
},
)(PluginContainer);