Files
flipper/src/plugin.js
Daniel Büchele cbab597236 show only one device in sidbar
Summary:
Refactors the plugin architecture of Sonar:
- Before plugin rendering had it's own implementation of the react lifecycle. This means the `render`-function was not called by react, but rather by the application it self. In this diff, the render method is now called from react, which enables better debugging and allows react to do optimizations.
- Business logic for querying emulators is moved away from the view components into its own dispatcher
- All plugin handling is moved from `App.js` to `PluginContainer`.
- The sidebar only shows one selected device. This allows us to add the screenshot feature as part of the Sonar main app and not a plugin.
- This also fixes the inconsistency between the devices button and the sidebar

Reviewed By: jknoxville

Differential Revision: D8186933

fbshipit-source-id: 46404443025bcf18d6eeba0679e098d5440822d5
2018-06-25 10:04:00 -07:00

180 lines
4.3 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 {KeyboardActions} from './MenuBar.js';
import type {App} from './App.js';
import type Logger from './fb-stubs/Logger.js';
import type Client from './Client.js';
import React from 'react';
import BaseDevice from './devices/BaseDevice.js';
import {AndroidDevice, IOSDevice} from 'sonar';
const invariant = require('invariant');
export type PluginClient = {|
send: (method: string, params?: Object) => void,
call: (method: string, params?: Object) => Promise<any>,
subscribe: (method: string, callback: (params: any) => void) => void,
|};
type PluginTarget = BaseDevice | Client;
export type Props<T> = {
logger: Logger,
persistedState: T,
setPersistedState: (state: $Shape<T>) => void,
};
export class SonarBasePlugin<
State = *,
Actions = *,
PersistedState = *,
> extends React.Component<Props<PersistedState>, State> {
static title: string = 'Unknown';
static id: string = 'Unknown';
static icon: string = 'apps';
static keyboardActions: ?KeyboardActions;
static screenshot: ?string;
// forbid instance properties that should be static
title: empty;
id: empty;
persist: empty;
icon: empty;
keyboardActions: empty;
screenshot: empty;
reducers: {
[actionName: string]: (state: State, actionData: Object) => $Shape<State>,
} = {};
app: App;
onKeyboardAction: ?(action: string) => void;
toJSON() {
return this.constructor.title;
}
// methods to be overriden by plugins
init(): void {}
teardown(): void {}
// methods to be overridden by subclasses
_init(): void {}
_teardown(): void {}
_setup(target: PluginTarget) {}
dispatchAction(actionData: Actions) {
// $FlowFixMe
const action = this.reducers[actionData.type];
if (!action) {
// $FlowFixMe
throw new ReferenceError(`Unknown action ${actionData.type}`);
}
if (typeof action === 'function') {
this.setState(action.call(this, this.state, actionData));
} else {
// $FlowFixMe
throw new TypeError(`Reducer ${actionData.type} isn't a function`);
}
}
}
export class SonarDevicePlugin<S = *, A = *> extends SonarBasePlugin<S, A> {
device: BaseDevice;
_setup(target: PluginTarget) {
invariant(target instanceof BaseDevice, 'expected instanceof Client');
const device: BaseDevice = target;
this.device = device;
super._setup(device);
}
_init() {
this.init();
}
}
export class SonarPlugin<S = *, A = *> extends SonarBasePlugin<S, A> {
constructor() {
super();
this.subscriptions = [];
}
subscriptions: Array<{
method: string,
callback: Function,
}>;
client: PluginClient;
realClient: Client;
getDevice(): ?BaseDevice {
return this.realClient.getDevice();
}
getAndroidDevice(): AndroidDevice {
const device = this.getDevice();
invariant(
device != null && device instanceof AndroidDevice,
'expected android device',
);
return device;
}
getIOSDevice() {
const device = this.getDevice();
invariant(
device != null && device instanceof IOSDevice,
'expected ios device',
);
return device;
}
_setup(target: any) {
/* We have to type the above as `any` since if we import the actual Client we have an
unresolvable dependency cycle */
const realClient: Client = target;
const id: string = this.constructor.id;
this.realClient = realClient;
this.client = {
call: (method, params) => realClient.call(id, method, params),
send: (method, params) => realClient.send(id, method, params),
subscribe: (method, callback) => {
this.subscriptions.push({
method,
callback,
});
realClient.subscribe(id, method, callback);
},
};
super._setup(realClient);
}
_teardown() {
// automatically unsubscribe subscriptions
for (const {method, callback} of this.subscriptions) {
this.realClient.unsubscribe(this.constructor.id, method, callback);
}
// run plugin teardown
this.teardown();
if (this.realClient.connected) {
this.realClient.rawSend('deinit', {plugin: this.constructor.id});
}
}
_init() {
this.realClient.rawSend('init', {plugin: this.constructor.id});
this.init();
}
}