From f5dcaf02a457e2305630349c625a7f78fc1c7294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=BCchele?= Date: Tue, 10 Jul 2018 02:22:16 -0700 Subject: [PATCH] persisted plugins state Summary: Two pros are passed into every plugin to persist state: - `this.props.persistedState` which is the object of the persisted state - `this.props.setPersistedState` which can be used to modify the persisted state The state itself is stored in redux and therefore persisted when switching plugins. The lifecycle hooks used a HOC are now implemented by the `ref`-function, which makes the code a little cleaner. Reviewed By: jknoxville Differential Revision: D8752097 fbshipit-source-id: d4f081f149cd840a29f1132bde91d72d3fba67ed --- src/PluginContainer.js | 132 ++++++++++++++++------------------- src/reducers/pluginStates.js | 5 +- 2 files changed, 63 insertions(+), 74 deletions(-) diff --git a/src/PluginContainer.js b/src/PluginContainer.js index fed6d7cc3..6ae6435ae 100644 --- a/src/PluginContainer.js +++ b/src/PluginContainer.js @@ -8,7 +8,6 @@ import type {SonarPlugin, SonarBasePlugin} 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.js'; import {SonarDevicePlugin} from './plugin.js'; import {ErrorBoundary, Component, FlexColumn, FlexRow, colors} from 'sonar'; @@ -51,78 +50,67 @@ type State = { pluginKey: string, }; -function withPluginLifecycleHooks( - PluginComponent: Class>, - target: Client | BaseDevice, -) { - return class extends React.Component> { - plugin: ?SonarBasePlugin<>; - - static displayName = `${PluginComponent.title}Plugin`; - - componentDidMount() { - const {plugin} = this; - if (plugin) { - activateMenuItems(plugin); - plugin._setup(target); - plugin._init(); - } - } - - componentWillUnmount() { - if (this.plugin) { - this.plugin._teardown(); - } - } - - render() { - return ( - ) => { - if (ref) { - this.plugin = ref; - } - }} - {...this.props} - /> +function computeState(props: Props): State { + // plugin changed + let activePlugin = devicePlugins.find( + (p: Class>) => 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 = plugins.find( + (p: Class>) => 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 { - static getDerivedStateFromProps(props: Props) { - let activePlugin = devicePlugins.find( - (p: Class>) => 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 = plugins.find( - (p: Class>) => p.id === props.selectedPlugin, - ); - if (!activePlugin || !target) { - return null; - } - pluginKey = `${target.id}#${activePlugin.id}`; - } + plugin: ?SonarBasePlugin<>; - return { - pluginKey, - activePlugin, - target, - }; + constructor(props: Props) { + super(); + this.state = computeState(props); } - state = { - pluginKey: 'unknown', - activePlugin: null, - target: null, + 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: ?SonarBasePlugin<>) => { + if (this.plugin) { + this.plugin._teardown(); + this.plugin = null; + } + const {target} = this.state; + if (ref && target) { + activateMenuItems(ref); + ref._setup(target); + ref._init(); + this.plugin = ref; + } }; render() { @@ -142,15 +130,13 @@ class PluginContainer extends Component { activePlugin.title }" encountered an error during render`} logger={this.props.logger}> - {React.createElement( - withPluginLifecycleHooks(activePlugin, target), - { - key: pluginKey, - logger: this.props.logger, - persistedState: pluginStates[pluginKey], - setPersistedState: state => setPluginState({pluginKey, state}), - }, - )} + {React.createElement(activePlugin, { + key: pluginKey, + logger: this.props.logger, + persistedState: pluginStates[pluginKey] || {}, + setPersistedState: state => setPluginState({pluginKey, state}), + ref: this.refChanged, + })} diff --git a/src/reducers/pluginStates.js b/src/reducers/pluginStates.js index fc9aeee8e..c264d255c 100644 --- a/src/reducers/pluginStates.js +++ b/src/reducers/pluginStates.js @@ -26,7 +26,10 @@ export default function reducer( if (action.type === 'SET_PLUGIN_STATE') { return { ...state, - [action.payload.pluginKey]: action.payload.state, + [action.payload.pluginKey]: { + ...state[action.payload.pluginKey], + ...action.payload.state, + }, }; } else { return state;