Device plugin management (2/n): enable/disable, install/uninstall
Summary: *Stack summary*: this stack adds ability to manage device plugins in the same way as client plugins: install, update, uninstall, enable (star) and disable (unstar) them. *Diff summary*: implemented all plugin management actions for device plugins. Changelog: it is now possible to enable/disable and install/uninstall device plugins Reviewed By: mweststrate Differential Revision: D26337377 fbshipit-source-id: 7d1ed61a8dc5f3339e5e548c613b67bca0c27f4f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
899fcd0783
commit
4541cdc23b
@@ -48,7 +48,6 @@ import {processMessageQueue} from './utils/messageQueue';
|
|||||||
import {ToggleButton, SmallText, Layout} from './ui';
|
import {ToggleButton, SmallText, Layout} from './ui';
|
||||||
import {theme, TrackingScope, _SandyPluginRenderer} from 'flipper-plugin';
|
import {theme, TrackingScope, _SandyPluginRenderer} from 'flipper-plugin';
|
||||||
import {isDevicePluginDefinition} from './utils/pluginUtils';
|
import {isDevicePluginDefinition} from './utils/pluginUtils';
|
||||||
import ArchivedDevice from './devices/ArchivedDevice';
|
|
||||||
import {ContentContainer} from './sandy-chrome/ContentContainer';
|
import {ContentContainer} from './sandy-chrome/ContentContainer';
|
||||||
import {Alert, Typography} from 'antd';
|
import {Alert, Typography} from 'antd';
|
||||||
import {InstalledPluginDetails} from 'plugin-lib';
|
import {InstalledPluginDetails} from 'plugin-lib';
|
||||||
@@ -318,7 +317,7 @@ class PluginContainer extends PureComponent<Props, State> {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.starPlugin({
|
this.props.starPlugin({
|
||||||
plugin: activePlugin,
|
plugin: activePlugin,
|
||||||
selectedApp: (this.props.target as Client).query.app,
|
selectedApp: (this.props.target as Client)?.query?.app,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
large
|
large
|
||||||
@@ -554,6 +553,7 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
|||||||
clients,
|
clients,
|
||||||
deepLinkPayload,
|
deepLinkPayload,
|
||||||
userStarredPlugins,
|
userStarredPlugins,
|
||||||
|
userStarredDevicePlugins,
|
||||||
},
|
},
|
||||||
pluginStates,
|
pluginStates,
|
||||||
plugins: {devicePlugins, clientPlugins, installedPlugins},
|
plugins: {devicePlugins, clientPlugins, installedPlugins},
|
||||||
@@ -567,23 +567,25 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
|||||||
|
|
||||||
if (selectedPlugin) {
|
if (selectedPlugin) {
|
||||||
activePlugin = devicePlugins.get(selectedPlugin);
|
activePlugin = devicePlugins.get(selectedPlugin);
|
||||||
target = selectedDevice;
|
|
||||||
if (selectedDevice && activePlugin) {
|
if (selectedDevice && activePlugin) {
|
||||||
|
target = selectedDevice;
|
||||||
pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id);
|
pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id);
|
||||||
pluginIsEnabled = true;
|
|
||||||
} else {
|
} else {
|
||||||
target =
|
target =
|
||||||
clients.find((client: Client) => client.id === selectedApp) || null;
|
clients.find((client: Client) => client.id === selectedApp) || null;
|
||||||
activePlugin = clientPlugins.get(selectedPlugin);
|
activePlugin = clientPlugins.get(selectedPlugin);
|
||||||
if (activePlugin && target) {
|
if (activePlugin && target) {
|
||||||
pluginKey = getPluginKey(target.id, activePlugin.id);
|
pluginKey = getPluginKey(target.id, activePlugin.id);
|
||||||
pluginIsEnabled = pluginIsStarred(
|
|
||||||
userStarredPlugins,
|
|
||||||
selectedApp,
|
|
||||||
activePlugin.id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pluginIsEnabled =
|
||||||
|
activePlugin !== undefined &&
|
||||||
|
pluginIsStarred(
|
||||||
|
userStarredPlugins,
|
||||||
|
userStarredDevicePlugins,
|
||||||
|
selectedApp,
|
||||||
|
activePlugin.id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const isArchivedDevice = !selectedDevice
|
const isArchivedDevice = !selectedDevice
|
||||||
? false
|
? false
|
||||||
|
|||||||
@@ -961,7 +961,8 @@ test('Sandy plugins support isPluginSupported + selectPlugin', async () => {
|
|||||||
expect(pluginInstance.deactivatedStub).toBeCalledTimes(0);
|
expect(pluginInstance.deactivatedStub).toBeCalledTimes(0);
|
||||||
expect(linksSeen).toEqual([]);
|
expect(linksSeen).toEqual([]);
|
||||||
|
|
||||||
// open a device plugin
|
// star and navigate to a device plugin
|
||||||
|
store.dispatch(starPlugin({plugin: definition3}));
|
||||||
pluginInstance.selectPlugin(definition3.id);
|
pluginInstance.selectPlugin(definition3.id);
|
||||||
expect(store.getState().connections.selectedPlugin).toBe(definition3.id);
|
expect(store.getState().connections.selectedPlugin).toBe(definition3.id);
|
||||||
expect(renderer.baseElement.querySelector('h1')).toMatchInlineSnapshot(`
|
expect(renderer.baseElement.querySelector('h1')).toMatchInlineSnapshot(`
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Object {
|
|||||||
"userPreferredApp": "TestApp#Android#MockAndroidDevice#serial",
|
"userPreferredApp": "TestApp#Android#MockAndroidDevice#serial",
|
||||||
"userPreferredDevice": "MockAndroidDevice",
|
"userPreferredDevice": "MockAndroidDevice",
|
||||||
"userPreferredPlugin": "TestPlugin",
|
"userPreferredPlugin": "TestPlugin",
|
||||||
|
"userStarredDevicePlugins": Set {},
|
||||||
"userStarredPlugins": Object {
|
"userStarredPlugins": Object {
|
||||||
"TestApp": Array [
|
"TestApp": Array [
|
||||||
"TestPlugin",
|
"TestPlugin",
|
||||||
|
|||||||
@@ -248,7 +248,10 @@ export default class BaseDevice {
|
|||||||
instance.destroy();
|
instance.destroy();
|
||||||
this.sandyPluginStates.delete(pluginId);
|
this.sandyPluginStates.delete(pluginId);
|
||||||
}
|
}
|
||||||
this.devicePlugins.splice(this.devicePlugins.indexOf(pluginId), 1);
|
const index = this.devicePlugins.indexOf(pluginId);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.devicePlugins.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import * as TestPlugin from '../../test-utils/TestPlugin';
|
|||||||
import {_SandyPluginDefinition as SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition as SandyPluginDefinition} from 'flipper-plugin';
|
||||||
import MockFlipper from '../../test-utils/MockFlipper';
|
import MockFlipper from '../../test-utils/MockFlipper';
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
|
import React from 'react';
|
||||||
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
|
|
||||||
const pluginDetails1 = TestUtils.createMockPluginDetails({
|
const pluginDetails1 = TestUtils.createMockPluginDetails({
|
||||||
id: 'plugin1',
|
id: 'plugin1',
|
||||||
@@ -45,10 +47,27 @@ const pluginDetails2 = TestUtils.createMockPluginDetails({
|
|||||||
});
|
});
|
||||||
const pluginDefinition2 = new SandyPluginDefinition(pluginDetails2, TestPlugin);
|
const pluginDefinition2 = new SandyPluginDefinition(pluginDetails2, TestPlugin);
|
||||||
|
|
||||||
|
const devicePluginDetails = TestUtils.createMockPluginDetails({
|
||||||
|
id: 'device',
|
||||||
|
name: 'flipper-device',
|
||||||
|
});
|
||||||
|
const devicePluginDefinition = new SandyPluginDefinition(devicePluginDetails, {
|
||||||
|
supportsDevice() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
devicePlugin() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
Component() {
|
||||||
|
return <h1>Plugin3</h1>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const mockedRequirePlugin = mocked(requirePlugin);
|
const mockedRequirePlugin = mocked(requirePlugin);
|
||||||
|
|
||||||
let mockFlipper: MockFlipper;
|
let mockFlipper: MockFlipper;
|
||||||
let mockClient: Client;
|
let mockClient: Client;
|
||||||
|
let mockDevice: BaseDevice;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockedRequirePlugin.mockImplementation(
|
mockedRequirePlugin.mockImplementation(
|
||||||
@@ -59,6 +78,8 @@ beforeEach(async () => {
|
|||||||
? pluginDefinition2
|
? pluginDefinition2
|
||||||
: details === pluginDetails1V2
|
: details === pluginDetails1V2
|
||||||
? pluginDefinition1V2
|
? pluginDefinition1V2
|
||||||
|
: details === devicePluginDetails
|
||||||
|
? devicePluginDefinition
|
||||||
: undefined)!,
|
: undefined)!,
|
||||||
);
|
);
|
||||||
mockFlipper = new MockFlipper();
|
mockFlipper = new MockFlipper();
|
||||||
@@ -66,6 +87,7 @@ beforeEach(async () => {
|
|||||||
clientOptions: {supportedPlugins: ['plugin1', 'plugin2']},
|
clientOptions: {supportedPlugins: ['plugin1', 'plugin2']},
|
||||||
});
|
});
|
||||||
mockClient = initResult.client;
|
mockClient = initResult.client;
|
||||||
|
mockDevice = initResult.device;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -199,3 +221,46 @@ test('unstar plugin', async () => {
|
|||||||
).not.toContain('plugin1');
|
).not.toContain('plugin1');
|
||||||
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
|
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('star device plugin', async () => {
|
||||||
|
mockFlipper.dispatch(
|
||||||
|
loadPlugin({
|
||||||
|
plugin: devicePluginDetails,
|
||||||
|
enable: false,
|
||||||
|
notifyIfFailed: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mockFlipper.dispatch(
|
||||||
|
starPlugin({
|
||||||
|
plugin: devicePluginDefinition,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
mockFlipper.getState().connections.userStarredDevicePlugins.has('device'),
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(mockDevice.sandyPluginStates.has('device')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unstar device plugin', async () => {
|
||||||
|
mockFlipper.dispatch(
|
||||||
|
loadPlugin({
|
||||||
|
plugin: devicePluginDetails,
|
||||||
|
enable: false,
|
||||||
|
notifyIfFailed: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mockFlipper.dispatch(
|
||||||
|
starPlugin({
|
||||||
|
plugin: devicePluginDefinition,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mockFlipper.dispatch(
|
||||||
|
starPlugin({
|
||||||
|
plugin: devicePluginDefinition,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
mockFlipper.getState().connections.userStarredDevicePlugins.has('device'),
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(mockDevice.sandyPluginStates.has('device')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import {clearPluginState} from '../reducers/pluginStates';
|
|||||||
import type {Logger} from '../fb-interfaces/Logger';
|
import type {Logger} from '../fb-interfaces/Logger';
|
||||||
import {
|
import {
|
||||||
LoadPluginActionPayload,
|
LoadPluginActionPayload,
|
||||||
PluginCommand,
|
|
||||||
UninstallPluginActionPayload,
|
UninstallPluginActionPayload,
|
||||||
UpdatePluginActionPayload,
|
UpdatePluginActionPayload,
|
||||||
pluginCommandsProcessed,
|
pluginCommandsProcessed,
|
||||||
StarPluginActionPayload,
|
StarPluginActionPayload,
|
||||||
|
PluginCommand,
|
||||||
} from '../reducers/pluginManager';
|
} from '../reducers/pluginManager';
|
||||||
import {
|
import {
|
||||||
getInstalledPlugins,
|
getInstalledPlugins,
|
||||||
@@ -28,8 +28,8 @@ import {sideEffect} from '../utils/sideEffect';
|
|||||||
import {requirePlugin} from './plugins';
|
import {requirePlugin} from './plugins';
|
||||||
import {showErrorNotification} from '../utils/notifications';
|
import {showErrorNotification} from '../utils/notifications';
|
||||||
import {
|
import {
|
||||||
|
ClientPluginDefinition,
|
||||||
DevicePluginDefinition,
|
DevicePluginDefinition,
|
||||||
FlipperDevicePlugin,
|
|
||||||
FlipperPlugin,
|
FlipperPlugin,
|
||||||
PluginDefinition,
|
PluginDefinition,
|
||||||
} from '../plugin';
|
} from '../plugin';
|
||||||
@@ -41,11 +41,17 @@ import {
|
|||||||
registerInstalledPlugins,
|
registerInstalledPlugins,
|
||||||
} from '../reducers/plugins';
|
} from '../reducers/plugins';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||||
import {pluginStarred, pluginUnstarred} from '../reducers/connections';
|
import {
|
||||||
|
devicePluginStarred,
|
||||||
|
devicePluginUnstarred,
|
||||||
|
pluginStarred,
|
||||||
|
pluginUnstarred,
|
||||||
|
} from '../reducers/connections';
|
||||||
import {deconstructClientId} from '../utils/clientUtils';
|
import {deconstructClientId} from '../utils/clientUtils';
|
||||||
import {clearMessageQueue} from '../reducers/pluginMessageQueue';
|
import {clearMessageQueue} from '../reducers/pluginMessageQueue';
|
||||||
import {
|
import {
|
||||||
getPluginKey,
|
getPluginKey,
|
||||||
|
isDevicePluginDefinition,
|
||||||
defaultEnabledBackgroundPlugins,
|
defaultEnabledBackgroundPlugins,
|
||||||
} from '../utils/pluginUtils';
|
} from '../utils/pluginUtils';
|
||||||
|
|
||||||
@@ -161,7 +167,7 @@ function uninstallPlugin(store: Store, {plugin}: UninstallPluginActionPayload) {
|
|||||||
function updatePlugin(store: Store, payload: UpdatePluginActionPayload) {
|
function updatePlugin(store: Store, payload: UpdatePluginActionPayload) {
|
||||||
const {plugin, enablePlugin} = payload;
|
const {plugin, enablePlugin} = payload;
|
||||||
if (isDevicePluginDefinition(plugin)) {
|
if (isDevicePluginDefinition(plugin)) {
|
||||||
return updateDevicePlugin(store, plugin);
|
return updateDevicePlugin(store, plugin, enablePlugin);
|
||||||
} else {
|
} else {
|
||||||
return updateClientPlugin(store, plugin, enablePlugin);
|
return updateClientPlugin(store, plugin, enablePlugin);
|
||||||
}
|
}
|
||||||
@@ -175,8 +181,26 @@ function getSelectedAppId(store: Store) {
|
|||||||
return selectedApp;
|
return selectedApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function starPlugin(store: Store, payload: StarPluginActionPayload) {
|
function starPlugin(
|
||||||
const {plugin, selectedApp} = payload;
|
store: Store,
|
||||||
|
{plugin, selectedApp}: StarPluginActionPayload,
|
||||||
|
) {
|
||||||
|
if (isDevicePluginDefinition(plugin)) {
|
||||||
|
starDevicePlugin(store, plugin);
|
||||||
|
} else {
|
||||||
|
starClientPlugin(store, plugin, selectedApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function starClientPlugin(
|
||||||
|
store: Store,
|
||||||
|
plugin: ClientPluginDefinition,
|
||||||
|
selectedApp: string | undefined,
|
||||||
|
) {
|
||||||
|
selectedApp = selectedApp ?? getSelectedAppId(store);
|
||||||
|
if (!selectedApp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const {connections} = store.getState();
|
const {connections} = store.getState();
|
||||||
const clients = connections.clients.filter(
|
const clients = connections.clients.filter(
|
||||||
(client) => client.query.app === selectedApp,
|
(client) => client.query.app === selectedApp,
|
||||||
@@ -200,6 +224,24 @@ function starPlugin(store: Store, payload: StarPluginActionPayload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function starDevicePlugin(store: Store, plugin: DevicePluginDefinition) {
|
||||||
|
const {connections} = store.getState();
|
||||||
|
const devicesWithPlugin = connections.devices.filter((d) =>
|
||||||
|
d.supportsPlugin(plugin.details),
|
||||||
|
);
|
||||||
|
if (connections.userStarredDevicePlugins.has(plugin.id)) {
|
||||||
|
devicesWithPlugin.forEach((d) => {
|
||||||
|
d.unloadDevicePlugin(plugin.id);
|
||||||
|
});
|
||||||
|
store.dispatch(devicePluginUnstarred(plugin));
|
||||||
|
} else {
|
||||||
|
devicesWithPlugin.forEach((d) => {
|
||||||
|
d.loadDevicePlugin(plugin);
|
||||||
|
});
|
||||||
|
store.dispatch(devicePluginStarred(plugin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateClientPlugin(
|
function updateClientPlugin(
|
||||||
store: Store,
|
store: Store,
|
||||||
plugin: typeof FlipperPlugin,
|
plugin: typeof FlipperPlugin,
|
||||||
@@ -235,9 +277,16 @@ function updateClientPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDevicePlugin(store: Store, plugin: DevicePluginDefinition) {
|
function updateDevicePlugin(
|
||||||
const devices = store.getState().connections.devices;
|
store: Store,
|
||||||
const devicesWithEnabledPlugin = devices.filter((d) =>
|
plugin: DevicePluginDefinition,
|
||||||
|
enable: boolean,
|
||||||
|
) {
|
||||||
|
if (enable) {
|
||||||
|
store.dispatch(devicePluginStarred(plugin));
|
||||||
|
}
|
||||||
|
const connections = store.getState().connections;
|
||||||
|
const devicesWithEnabledPlugin = connections.devices.filter((d) =>
|
||||||
d.supportsPlugin(plugin),
|
d.supportsPlugin(plugin),
|
||||||
);
|
);
|
||||||
devicesWithEnabledPlugin.forEach((d) => {
|
devicesWithEnabledPlugin.forEach((d) => {
|
||||||
@@ -295,12 +344,3 @@ function unloadPluginModule(plugin: ActivatablePluginDetails) {
|
|||||||
}
|
}
|
||||||
unloadModule(plugin.entry);
|
unloadModule(plugin.entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDevicePluginDefinition(
|
|
||||||
definition: PluginDefinition,
|
|
||||||
): definition is DevicePluginDefinition {
|
|
||||||
return (
|
|
||||||
(definition as any).prototype instanceof FlipperDevicePlugin ||
|
|
||||||
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export type State = {
|
|||||||
userPreferredPlugin: null | string;
|
userPreferredPlugin: null | string;
|
||||||
userPreferredApp: null | string;
|
userPreferredApp: null | string;
|
||||||
userStarredPlugins: {[client: string]: string[]};
|
userStarredPlugins: {[client: string]: string[]};
|
||||||
|
userStarredDevicePlugins: Set<string>;
|
||||||
clients: Array<Client>;
|
clients: Array<Client>;
|
||||||
uninitializedClients: Array<{
|
uninitializedClients: Array<{
|
||||||
client: UninitializedClient;
|
client: UninitializedClient;
|
||||||
@@ -115,6 +116,12 @@ export type Action =
|
|||||||
selectedApp: string;
|
selectedApp: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'DEVICE_PLUGIN_STARRED';
|
||||||
|
payload: {
|
||||||
|
plugin: PluginDefinition;
|
||||||
|
};
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'PLUGIN_UNSTARRED';
|
type: 'PLUGIN_UNSTARRED';
|
||||||
payload: {
|
payload: {
|
||||||
@@ -122,6 +129,12 @@ export type Action =
|
|||||||
selectedApp: string;
|
selectedApp: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'DEVICE_PLUGIN_UNSTARRED';
|
||||||
|
payload: {
|
||||||
|
plugin: PluginDefinition;
|
||||||
|
};
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'SELECT_CLIENT';
|
type: 'SELECT_CLIENT';
|
||||||
payload: string | null;
|
payload: string | null;
|
||||||
@@ -140,6 +153,7 @@ const INITAL_STATE: State = {
|
|||||||
userPreferredPlugin: null,
|
userPreferredPlugin: null,
|
||||||
userPreferredApp: null,
|
userPreferredApp: null,
|
||||||
userStarredPlugins: {},
|
userStarredPlugins: {},
|
||||||
|
userStarredDevicePlugins: new Set(),
|
||||||
clients: [],
|
clients: [],
|
||||||
uninitializedClients: [],
|
uninitializedClients: [],
|
||||||
deepLinkPayload: null,
|
deepLinkPayload: null,
|
||||||
@@ -379,6 +393,12 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case 'DEVICE_PLUGIN_STARRED': {
|
||||||
|
const {plugin} = action.payload;
|
||||||
|
return produce(state, (draft) => {
|
||||||
|
draft.userStarredDevicePlugins.add(plugin.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
case 'PLUGIN_UNSTARRED': {
|
case 'PLUGIN_UNSTARRED': {
|
||||||
const {plugin, selectedApp} = action.payload;
|
const {plugin, selectedApp} = action.payload;
|
||||||
const selectedPlugin = plugin.id;
|
const selectedPlugin = plugin.id;
|
||||||
@@ -393,6 +413,12 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case 'DEVICE_PLUGIN_UNSTARRED': {
|
||||||
|
const {plugin} = action.payload;
|
||||||
|
return produce(state, (draft) => {
|
||||||
|
draft.userStarredDevicePlugins.delete(plugin.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -449,6 +475,20 @@ export const pluginStarred = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const devicePluginStarred = (plugin: PluginDefinition): Action => ({
|
||||||
|
type: 'DEVICE_PLUGIN_STARRED',
|
||||||
|
payload: {
|
||||||
|
plugin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const devicePluginUnstarred = (plugin: PluginDefinition): Action => ({
|
||||||
|
type: 'DEVICE_PLUGIN_UNSTARRED',
|
||||||
|
payload: {
|
||||||
|
plugin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const pluginUnstarred = (
|
export const pluginUnstarred = (
|
||||||
plugin: PluginDefinition,
|
plugin: PluginDefinition,
|
||||||
appId: string,
|
appId: string,
|
||||||
@@ -583,9 +623,13 @@ export function getSelectedPluginKey(state: State): string | undefined {
|
|||||||
|
|
||||||
export function pluginIsStarred(
|
export function pluginIsStarred(
|
||||||
userStarredPlugins: State['userStarredPlugins'],
|
userStarredPlugins: State['userStarredPlugins'],
|
||||||
|
userStarredDevicePlugins: State['userStarredDevicePlugins'],
|
||||||
app: string | null,
|
app: string | null,
|
||||||
pluginId: string,
|
pluginId: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
if (userStarredDevicePlugins.has(pluginId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!app) {
|
if (!app) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,9 @@ export default combineReducers<State, Actions>({
|
|||||||
'userPreferredPlugin',
|
'userPreferredPlugin',
|
||||||
'userPreferredApp',
|
'userPreferredApp',
|
||||||
'userStarredPlugins',
|
'userStarredPlugins',
|
||||||
|
'userStarredDevicePlugins',
|
||||||
],
|
],
|
||||||
|
transforms: [setTransformer({whitelist: ['userStarredDevicePlugins']})],
|
||||||
},
|
},
|
||||||
connections,
|
connections,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export type UpdatePluginAction = {
|
|||||||
|
|
||||||
export type StarPluginActionPayload = {
|
export type StarPluginActionPayload = {
|
||||||
plugin: PluginDefinition;
|
plugin: PluginDefinition;
|
||||||
selectedApp: string;
|
selectedApp?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StarPluginAction = {
|
export type StarPluginAction = {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export const PluginList = memo(function PluginList({
|
|||||||
client,
|
client,
|
||||||
plugins,
|
plugins,
|
||||||
connections.userStarredPlugins,
|
connections.userStarredPlugins,
|
||||||
|
connections.userStarredDevicePlugins,
|
||||||
pluginsChanged,
|
pluginsChanged,
|
||||||
]);
|
]);
|
||||||
const isConnected = useValue(activeDevice?.connected, false);
|
const isConnected = useValue(activeDevice?.connected, false);
|
||||||
@@ -143,14 +144,16 @@ export const PluginList = memo(function PluginList({
|
|||||||
);
|
);
|
||||||
const handleStarPlugin = useCallback(
|
const handleStarPlugin = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
|
const plugin = (plugins.clientPlugins.get(id) ??
|
||||||
|
plugins.devicePlugins.get(id))!;
|
||||||
dispatch(
|
dispatch(
|
||||||
starPlugin({
|
starPlugin({
|
||||||
selectedApp: client!.query.app,
|
selectedApp: client?.query.app,
|
||||||
plugin: plugins.clientPlugins.get(id)!,
|
plugin,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[client, plugins.clientPlugins, dispatch],
|
[client, plugins.clientPlugins, plugins.devicePlugins, dispatch],
|
||||||
);
|
);
|
||||||
const handleInstallPlugin = useCallback(
|
const handleInstallPlugin = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
@@ -200,6 +203,18 @@ export const PluginList = memo(function PluginList({
|
|||||||
}
|
}
|
||||||
onClick={handleAppPluginClick}
|
onClick={handleAppPluginClick}
|
||||||
tooltip={getPluginTooltip(plugin.details)}
|
tooltip={getPluginTooltip(plugin.details)}
|
||||||
|
actions={
|
||||||
|
isArchived ? null : (
|
||||||
|
<ActionButton
|
||||||
|
id={plugin.id}
|
||||||
|
onClick={handleStarPlugin}
|
||||||
|
title="Disable plugin"
|
||||||
|
icon={
|
||||||
|
<MinusOutlined size={16} style={{marginRight: 0}} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</PluginGroup>
|
</PluginGroup>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {FlipperPlugin} from '../../../plugin';
|
|||||||
import MetroDevice from '../../../devices/MetroDevice';
|
import MetroDevice from '../../../devices/MetroDevice';
|
||||||
import BaseDevice from '../../../devices/BaseDevice';
|
import BaseDevice from '../../../devices/BaseDevice';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||||
import {createMockPluginDetails} from 'flipper-plugin/src/test-utils/test-utils';
|
import {TestUtils} from 'flipper-plugin';
|
||||||
import {selectPlugin} from '../../../reducers/connections';
|
import {selectPlugin} from '../../../reducers/connections';
|
||||||
import {registerMetroDevice} from '../../../dispatcher/metroDevice';
|
import {registerMetroDevice} from '../../../dispatcher/metroDevice';
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +31,8 @@ import * as LogsPluginModule from '../../../../../plugins/logs/index';
|
|||||||
import {createMockDownloadablePluginDetails} from '../../../utils/testUtils';
|
import {createMockDownloadablePluginDetails} from '../../../utils/testUtils';
|
||||||
import {computePluginLists} from '../../../utils/pluginUtils';
|
import {computePluginLists} from '../../../utils/pluginUtils';
|
||||||
|
|
||||||
|
const createMockPluginDetails = TestUtils.createMockPluginDetails;
|
||||||
|
|
||||||
const logsPlugin = new _SandyPluginDefinition(
|
const logsPlugin = new _SandyPluginDefinition(
|
||||||
createMockPluginDetails({id: 'DeviceLogs'}),
|
createMockPluginDetails({id: 'DeviceLogs'}),
|
||||||
LogsPluginModule,
|
LogsPluginModule,
|
||||||
@@ -173,6 +175,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
flipper.client,
|
flipper.client,
|
||||||
state.plugins,
|
state.plugins,
|
||||||
state.connections.userStarredPlugins,
|
state.connections.userStarredPlugins,
|
||||||
|
state.connections.userStarredDevicePlugins,
|
||||||
),
|
),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
downloadablePlugins: [],
|
downloadablePlugins: [],
|
||||||
@@ -284,6 +287,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
flipper.client,
|
flipper.client,
|
||||||
state.plugins,
|
state.plugins,
|
||||||
state.connections.userStarredPlugins,
|
state.connections.userStarredPlugins,
|
||||||
|
state.connections.userStarredDevicePlugins,
|
||||||
);
|
);
|
||||||
expect(pluginLists).toEqual({
|
expect(pluginLists).toEqual({
|
||||||
devicePlugins: [logsPlugin],
|
devicePlugins: [logsPlugin],
|
||||||
@@ -297,15 +301,15 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
unsupportedDevicePlugin.details,
|
unsupportedDevicePlugin.details,
|
||||||
"Device plugin 'Unsupported Device Plugin' is not supported by the current device type.",
|
"Device plugin 'Unsupported Device Plugin' is not supported by the currently connected device.",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
unsupportedPlugin.details,
|
unsupportedPlugin.details,
|
||||||
"Plugin 'Unsupported Plugin' is installed in Flipper, but not supported by the client application",
|
"Plugin 'Unsupported Plugin' is not supported by the client application",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
unsupportedDownloadablePlugin,
|
unsupportedDownloadablePlugin,
|
||||||
"Plugin 'Unsupported Uninstalled Plugin' is not installed in Flipper and not supported by the client application",
|
"Plugin 'Unsupported Uninstalled Plugin' is not supported by the client application and not installed in Flipper",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
downloadablePlugins: [supportedDownloadablePlugin],
|
downloadablePlugins: [supportedDownloadablePlugin],
|
||||||
@@ -325,6 +329,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
flipper.client,
|
flipper.client,
|
||||||
state.plugins,
|
state.plugins,
|
||||||
state.connections.userStarredPlugins,
|
state.connections.userStarredPlugins,
|
||||||
|
state.connections.userStarredDevicePlugins,
|
||||||
),
|
),
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
enabledPlugins: [plugin2],
|
enabledPlugins: [plugin2],
|
||||||
|
|||||||
@@ -62,6 +62,23 @@ type MockOptions = Partial<{
|
|||||||
supportedPlugins?: string[];
|
supportedPlugins?: string[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
function isPluginEnabled(
|
||||||
|
store: Store,
|
||||||
|
pluginClazz: PluginDefinition,
|
||||||
|
selectedApp: string,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
(!isDevicePluginDefinition(pluginClazz) &&
|
||||||
|
store
|
||||||
|
.getState()
|
||||||
|
.connections.userStarredPlugins[selectedApp]?.includes(
|
||||||
|
pluginClazz.id,
|
||||||
|
)) ||
|
||||||
|
(isDevicePluginDefinition(pluginClazz) &&
|
||||||
|
store.getState().connections.userStarredDevicePlugins.has(pluginClazz.id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function createMockFlipperWithPlugin(
|
export async function createMockFlipperWithPlugin(
|
||||||
pluginClazz: PluginDefinition,
|
pluginClazz: PluginDefinition,
|
||||||
options?: MockOptions,
|
options?: MockOptions,
|
||||||
@@ -89,14 +106,7 @@ export async function createMockFlipperWithPlugin(
|
|||||||
backgroundPlugins: options?.asBackgroundPlugin ? [pluginClazz.id] : [],
|
backgroundPlugins: options?.asBackgroundPlugin ? [pluginClazz.id] : [],
|
||||||
});
|
});
|
||||||
// enable the plugin
|
// enable the plugin
|
||||||
if (
|
if (!isPluginEnabled(store, pluginClazz, name)) {
|
||||||
!isDevicePluginDefinition(pluginClazz) &&
|
|
||||||
!store
|
|
||||||
.getState()
|
|
||||||
.connections.userStarredPlugins[client.query.app]?.includes(
|
|
||||||
pluginClazz.id,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
starPlugin({
|
starPlugin({
|
||||||
plugin: pluginClazz,
|
plugin: pluginClazz,
|
||||||
@@ -106,7 +116,7 @@ export async function createMockFlipperWithPlugin(
|
|||||||
}
|
}
|
||||||
if (!options?.dontEnableAdditionalPlugins) {
|
if (!options?.dontEnableAdditionalPlugins) {
|
||||||
options?.additionalPlugins?.forEach((plugin) => {
|
options?.additionalPlugins?.forEach((plugin) => {
|
||||||
if (!isDevicePluginDefinition(plugin)) {
|
if (!isPluginEnabled(store, plugin, name)) {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
starPlugin({
|
starPlugin({
|
||||||
plugin,
|
plugin,
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export function processMessagesLater(
|
|||||||
case (plugin as any).prototype instanceof FlipperDevicePlugin:
|
case (plugin as any).prototype instanceof FlipperDevicePlugin:
|
||||||
case pluginIsStarred(
|
case pluginIsStarred(
|
||||||
store.getState().connections.userStarredPlugins,
|
store.getState().connections.userStarredPlugins,
|
||||||
|
store.getState().connections.userStarredDevicePlugins,
|
||||||
deconstructPluginKey(pluginKey).client,
|
deconstructPluginKey(pluginKey).client,
|
||||||
pluginId,
|
pluginId,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export function getExportablePlugins(
|
|||||||
client,
|
client,
|
||||||
state.plugins,
|
state.plugins,
|
||||||
state.connections.userStarredPlugins,
|
state.connections.userStarredPlugins,
|
||||||
|
state.connections.userStarredDevicePlugins,
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -179,16 +180,33 @@ export function computePluginLists(
|
|||||||
client: Client | undefined,
|
client: Client | undefined,
|
||||||
plugins: State['plugins'],
|
plugins: State['plugins'],
|
||||||
userStarredPlugins: State['connections']['userStarredPlugins'],
|
userStarredPlugins: State['connections']['userStarredPlugins'],
|
||||||
|
userStarredDevicePlugins: Set<string>,
|
||||||
_pluginsChanged?: number, // this argument is purely used to invalidate the memoization cache
|
_pluginsChanged?: number, // this argument is purely used to invalidate the memoization cache
|
||||||
) {
|
) {
|
||||||
|
const uninstalledMarketplacePlugins = filterNewestVersionOfEachPlugin(
|
||||||
|
[...plugins.bundledPlugins.values()],
|
||||||
|
plugins.marketplacePlugins,
|
||||||
|
).filter((p) => !plugins.loadedPlugins.has(p.id));
|
||||||
const devicePlugins: DevicePluginDefinition[] = [
|
const devicePlugins: DevicePluginDefinition[] = [
|
||||||
...plugins.devicePlugins.values(),
|
...plugins.devicePlugins.values(),
|
||||||
].filter((p) => device?.supportsPlugin(p));
|
]
|
||||||
|
.filter((p) => device?.supportsPlugin(p))
|
||||||
|
.filter((p) => userStarredDevicePlugins.has(p.id));
|
||||||
const metroPlugins: DevicePluginDefinition[] = [
|
const metroPlugins: DevicePluginDefinition[] = [
|
||||||
...plugins.devicePlugins.values(),
|
...plugins.devicePlugins.values(),
|
||||||
].filter((p) => metroDevice?.supportsPlugin(p));
|
]
|
||||||
|
.filter((p) => metroDevice?.supportsPlugin(p))
|
||||||
|
.filter((p) => userStarredDevicePlugins.has(p.id));
|
||||||
const enabledPlugins: ClientPluginDefinition[] = [];
|
const enabledPlugins: ClientPluginDefinition[] = [];
|
||||||
const disabledPlugins: ClientPluginDefinition[] = [];
|
const disabledPlugins: PluginDefinition[] = [
|
||||||
|
...plugins.devicePlugins.values(),
|
||||||
|
]
|
||||||
|
.filter(
|
||||||
|
(p) =>
|
||||||
|
device?.supportsPlugin(p.details) ||
|
||||||
|
metroDevice?.supportsPlugin(p.details),
|
||||||
|
)
|
||||||
|
.filter((p) => !userStarredDevicePlugins.has(p.id));
|
||||||
const unavailablePlugins: [plugin: PluginDetails, reason: string][] = [];
|
const unavailablePlugins: [plugin: PluginDetails, reason: string][] = [];
|
||||||
const downloadablePlugins: (
|
const downloadablePlugins: (
|
||||||
| DownloadablePluginDetails
|
| DownloadablePluginDetails
|
||||||
@@ -203,10 +221,20 @@ export function computePluginLists(
|
|||||||
p.details,
|
p.details,
|
||||||
`Device plugin '${getPluginTitle(
|
`Device plugin '${getPluginTitle(
|
||||||
p.details,
|
p.details,
|
||||||
)}' is not supported by the current device type.`,
|
)}' is not supported by the currently connected device.`,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const plugin of uninstalledMarketplacePlugins.filter(
|
||||||
|
(d) => d.pluginType === 'device',
|
||||||
|
)) {
|
||||||
|
if (
|
||||||
|
device.supportsPlugin(plugin) ||
|
||||||
|
metroDevice?.supportsPlugin(plugin)
|
||||||
|
) {
|
||||||
|
downloadablePlugins.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process problematic plugins
|
// process problematic plugins
|
||||||
@@ -244,7 +272,7 @@ export function computePluginLists(
|
|||||||
plugin.details,
|
plugin.details,
|
||||||
`Plugin '${getPluginTitle(
|
`Plugin '${getPluginTitle(
|
||||||
plugin.details,
|
plugin.details,
|
||||||
)}' is installed in Flipper, but not supported by the client application`,
|
)}' is not supported by the client application`,
|
||||||
]);
|
]);
|
||||||
} else if (favoritePlugins.includes(plugin)) {
|
} else if (favoritePlugins.includes(plugin)) {
|
||||||
enabledPlugins.push(plugin);
|
enabledPlugins.push(plugin);
|
||||||
@@ -252,23 +280,25 @@ export function computePluginLists(
|
|||||||
disabledPlugins.push(plugin);
|
disabledPlugins.push(plugin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const uninstalledMarketplacePlugins = filterNewestVersionOfEachPlugin(
|
|
||||||
[...plugins.bundledPlugins.values()],
|
|
||||||
plugins.marketplacePlugins,
|
|
||||||
).filter((p) => !plugins.loadedPlugins.has(p.id));
|
|
||||||
uninstalledMarketplacePlugins.forEach((plugin) => {
|
uninstalledMarketplacePlugins.forEach((plugin) => {
|
||||||
if (client.supportsPlugin(plugin.id)) {
|
if (client.supportsPlugin(plugin.id)) {
|
||||||
downloadablePlugins.push(plugin);
|
downloadablePlugins.push(plugin);
|
||||||
} else {
|
|
||||||
unavailablePlugins.push([
|
|
||||||
plugin,
|
|
||||||
`Plugin '${getPluginTitle(
|
|
||||||
plugin,
|
|
||||||
)}' is not installed in Flipper and not supported by the client application`,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const downloadablePluginSet = new Set<string>(
|
||||||
|
downloadablePlugins.map((p) => p.id),
|
||||||
|
);
|
||||||
|
uninstalledMarketplacePlugins
|
||||||
|
.filter((p) => !downloadablePluginSet.has(p.id))
|
||||||
|
.forEach((plugin) => {
|
||||||
|
unavailablePlugins.push([
|
||||||
|
plugin,
|
||||||
|
`Plugin '${getPluginTitle(
|
||||||
|
plugin,
|
||||||
|
)}' is not supported by the client application and not installed in Flipper`,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
devicePlugins.sort(sortPluginsByName);
|
devicePlugins.sort(sortPluginsByName);
|
||||||
metroPlugins.sort(sortPluginsByName);
|
metroPlugins.sort(sortPluginsByName);
|
||||||
|
|||||||
Reference in New Issue
Block a user