Use selectors to compute plugin lists according to the selected device and app
Summary: Use selectors to re-compute and cache plugin lists according to the selected device and app. Reviewed By: mweststrate Differential Revision: D29247845 fbshipit-source-id: 4bc669d5d441d605c4090086c4ce59b6d9684a4c
This commit is contained in:
committed by
Facebook GitHub Bot
parent
1d26faeacb
commit
ff5d8ba29f
@@ -65,6 +65,7 @@
|
|||||||
"recursive-readdir": "^2.2.2",
|
"recursive-readdir": "^2.2.2",
|
||||||
"redux": "^4.1.0",
|
"redux": "^4.1.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
|
"reselect": "^4.0.0",
|
||||||
"rsocket-core": "^0.0.19",
|
"rsocket-core": "^0.0.19",
|
||||||
"rsocket-flowable": "^0.0.25",
|
"rsocket-flowable": "^0.0.25",
|
||||||
"rsocket-tcp-server": "^0.0.25",
|
"rsocket-tcp-server": "^0.0.25",
|
||||||
|
|||||||
@@ -2,22 +2,6 @@
|
|||||||
|
|
||||||
exports[`can create a Fake flipper 1`] = `
|
exports[`can create a Fake flipper 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"activeClient": Object {
|
|
||||||
"id": "TestApp#Android#MockAndroidDevice#serial",
|
|
||||||
"query": Object {
|
|
||||||
"app": "TestApp",
|
|
||||||
"device": "MockAndroidDevice",
|
|
||||||
"device_id": "serial",
|
|
||||||
"os": "Android",
|
|
||||||
"sdk_version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"activeDevice": Object {
|
|
||||||
"deviceType": "physical",
|
|
||||||
"os": "Android",
|
|
||||||
"serial": "serial",
|
|
||||||
"title": "MockAndroidDevice",
|
|
||||||
},
|
|
||||||
"androidEmulators": Array [],
|
"androidEmulators": Array [],
|
||||||
"clients": Array [
|
"clients": Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -52,8 +36,8 @@ Object {
|
|||||||
"TestPlugin",
|
"TestPlugin",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"metroDevice": null,
|
|
||||||
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
||||||
|
"selectedAppPluginListRevision": 0,
|
||||||
"selectedDevice": Object {
|
"selectedDevice": Object {
|
||||||
"deviceType": "physical",
|
"deviceType": "physical",
|
||||||
"os": "Android",
|
"os": "Android",
|
||||||
@@ -90,22 +74,6 @@ Object {
|
|||||||
|
|
||||||
exports[`can create a Fake flipper with legacy wrapper 1`] = `
|
exports[`can create a Fake flipper with legacy wrapper 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"activeClient": Object {
|
|
||||||
"id": "TestApp#Android#MockAndroidDevice#serial",
|
|
||||||
"query": Object {
|
|
||||||
"app": "TestApp",
|
|
||||||
"device": "MockAndroidDevice",
|
|
||||||
"device_id": "serial",
|
|
||||||
"os": "Android",
|
|
||||||
"sdk_version": 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"activeDevice": Object {
|
|
||||||
"deviceType": "physical",
|
|
||||||
"os": "Android",
|
|
||||||
"serial": "serial",
|
|
||||||
"title": "MockAndroidDevice",
|
|
||||||
},
|
|
||||||
"androidEmulators": Array [],
|
"androidEmulators": Array [],
|
||||||
"clients": Array [
|
"clients": Array [
|
||||||
Object {
|
Object {
|
||||||
@@ -140,8 +108,8 @@ Object {
|
|||||||
"TestPlugin",
|
"TestPlugin",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"metroDevice": null,
|
|
||||||
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
||||||
|
"selectedAppPluginListRevision": 0,
|
||||||
"selectedDevice": Object {
|
"selectedDevice": Object {
|
||||||
"deviceType": "physical",
|
"deviceType": "physical",
|
||||||
"os": "Android",
|
"os": "Android",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {ShareType} from '../reducers/application';
|
|||||||
import {State as Store} from '../reducers';
|
import {State as Store} from '../reducers';
|
||||||
import {ActiveSheet} from '../reducers/application';
|
import {ActiveSheet} from '../reducers/application';
|
||||||
import {selectedPlugins as actionForSelectedPlugins} from '../reducers/plugins';
|
import {selectedPlugins as actionForSelectedPlugins} from '../reducers/plugins';
|
||||||
import {getExportablePlugins} from '../utils/pluginUtils';
|
|
||||||
import {
|
import {
|
||||||
ACTIVE_SHEET_SHARE_DATA,
|
ACTIVE_SHEET_SHARE_DATA,
|
||||||
setActiveSheet as getActiveSheetAction,
|
setActiveSheet as getActiveSheetAction,
|
||||||
@@ -23,6 +22,7 @@ import ListView from './ListView';
|
|||||||
import {Dispatch, Action} from 'redux';
|
import {Dispatch, Action} from 'redux';
|
||||||
import {unsetShare} from '../reducers/application';
|
import {unsetShare} from '../reducers/application';
|
||||||
import {FlexColumn, styled} from '../ui';
|
import {FlexColumn, styled} from '../ui';
|
||||||
|
import {getExportablePlugins} from '../selectors/connections';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
@@ -104,14 +104,7 @@ class ExportDataPluginSheet extends Component<Props, {}> {
|
|||||||
|
|
||||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||||
(state) => {
|
(state) => {
|
||||||
const selectedClient = state.connections.clients.find((o) => {
|
const availablePluginsToExport = getExportablePlugins(state);
|
||||||
return o.id === state.connections.selectedApp;
|
|
||||||
});
|
|
||||||
const availablePluginsToExport = getExportablePlugins(
|
|
||||||
state,
|
|
||||||
state.connections.selectedDevice ?? undefined,
|
|
||||||
selectedClient,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
share: state.application.share,
|
share: state.application.share,
|
||||||
selectedPlugins: state.plugins.selectedPlugins,
|
selectedPlugins: state.plugins.selectedPlugins,
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import {create, act, ReactTestRenderer} from 'react-test-renderer';
|
|||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
import ExportDataPluginSheet from '../ExportDataPluginSheet';
|
import ExportDataPluginSheet from '../ExportDataPluginSheet';
|
||||||
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
||||||
import {getExportablePlugins, getPluginKey} from '../../utils/pluginUtils';
|
import {getPluginKey} from '../../utils/pluginUtils';
|
||||||
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
|
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
|
||||||
import {setPluginState} from '../../reducers/pluginStates';
|
import {setPluginState} from '../../reducers/pluginStates';
|
||||||
|
import {getExportablePlugins} from '../../selectors/connections';
|
||||||
|
|
||||||
class TestPlugin extends FlipperPlugin<any, any, any> {
|
class TestPlugin extends FlipperPlugin<any, any, any> {
|
||||||
static details = {
|
static details = {
|
||||||
@@ -56,7 +57,7 @@ test('SettingsSheet snapshot with nothing enabled', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(getExportablePlugins(store.getState(), device, client)).toEqual([]);
|
expect(getExportablePlugins(store.getState())).toEqual([]);
|
||||||
|
|
||||||
// makes device plugin visible
|
// makes device plugin visible
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
@@ -66,7 +67,7 @@ test('SettingsSheet snapshot with nothing enabled', async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(getExportablePlugins(store.getState(), device, client)).toEqual([
|
expect(getExportablePlugins(store.getState())).toEqual([
|
||||||
{
|
{
|
||||||
id: 'TestDevicePlugin',
|
id: 'TestDevicePlugin',
|
||||||
label: 'TestDevicePlugin',
|
label: 'TestDevicePlugin',
|
||||||
@@ -86,7 +87,7 @@ test('SettingsSheet snapshot with nothing enabled', async () => {
|
|||||||
|
|
||||||
test('SettingsSheet snapshot with one plugin enabled', async () => {
|
test('SettingsSheet snapshot with one plugin enabled', async () => {
|
||||||
let root: ReactTestRenderer;
|
let root: ReactTestRenderer;
|
||||||
const {store, device, client, pluginKey} = await createMockFlipperWithPlugin(
|
const {store, device, pluginKey} = await createMockFlipperWithPlugin(
|
||||||
TestPlugin,
|
TestPlugin,
|
||||||
{
|
{
|
||||||
additionalPlugins: [TestDevicePlugin],
|
additionalPlugins: [TestDevicePlugin],
|
||||||
@@ -96,7 +97,7 @@ test('SettingsSheet snapshot with one plugin enabled', async () => {
|
|||||||
// enabled
|
// enabled
|
||||||
// in Sandy wrapper, a plugin is either persistable or not, but it doesn't depend on the current state.
|
// in Sandy wrapper, a plugin is either persistable or not, but it doesn't depend on the current state.
|
||||||
// So this plugin will show up, even though its state is still the default
|
// So this plugin will show up, even though its state is still the default
|
||||||
expect(getExportablePlugins(store.getState(), device, client)).toEqual([
|
expect(getExportablePlugins(store.getState())).toEqual([
|
||||||
{
|
{
|
||||||
id: 'TestPlugin',
|
id: 'TestPlugin',
|
||||||
label: 'TestPlugin',
|
label: 'TestPlugin',
|
||||||
@@ -115,7 +116,7 @@ test('SettingsSheet snapshot with one plugin enabled', async () => {
|
|||||||
state: {test: '1'},
|
state: {test: '1'},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(getExportablePlugins(store.getState(), device, client)).toEqual([
|
expect(getExportablePlugins(store.getState())).toEqual([
|
||||||
{
|
{
|
||||||
id: 'TestDevicePlugin',
|
id: 'TestDevicePlugin',
|
||||||
label: 'TestDevicePlugin',
|
label: 'TestDevicePlugin',
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {switchPlugin} from '../../reducers/pluginManager';
|
|||||||
import {isPluginEnabled} from '../../reducers/connections';
|
import {isPluginEnabled} from '../../reducers/connections';
|
||||||
import {theme} from 'flipper-plugin';
|
import {theme} from 'flipper-plugin';
|
||||||
import {PluginDefinition} from '../../plugin';
|
import {PluginDefinition} from '../../plugin';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import {getActiveClient} from '../../selectors/connections';
|
||||||
|
|
||||||
const Waiting = styled(Layout.Container)({
|
const Waiting = styled(Layout.Container)({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -30,7 +32,7 @@ export default function PluginInfo() {
|
|||||||
const enabledDevicePlugins = useStore(
|
const enabledDevicePlugins = useStore(
|
||||||
(state) => state.connections.enabledDevicePlugins,
|
(state) => state.connections.enabledDevicePlugins,
|
||||||
);
|
);
|
||||||
const activeClient = useStore((state) => state.connections.activeClient);
|
const activeClient = useSelector(getActiveClient);
|
||||||
const clientPlugins = useStore((state) => state.plugins.clientPlugins);
|
const clientPlugins = useStore((state) => state.plugins.clientPlugins);
|
||||||
const devicePlugins = useStore((state) => state.plugins.devicePlugins);
|
const devicePlugins = useStore((state) => state.plugins.devicePlugins);
|
||||||
const selectedClientId = activeClient?.id ?? null;
|
const selectedClientId = activeClient?.id ?? null;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import reactNative from './reactNative';
|
|||||||
import pluginMarketplace from './fb-stubs/pluginMarketplace';
|
import pluginMarketplace from './fb-stubs/pluginMarketplace';
|
||||||
import pluginDownloads from './pluginDownloads';
|
import pluginDownloads from './pluginDownloads';
|
||||||
import info from '../utils/info';
|
import info from '../utils/info';
|
||||||
import pluginLists from './pluginLists';
|
import pluginChangeListener from './pluginsChangeListener';
|
||||||
|
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
import {Store} from '../reducers/index';
|
import {Store} from '../reducers/index';
|
||||||
@@ -52,7 +52,7 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
|
|||||||
pluginMarketplace,
|
pluginMarketplace,
|
||||||
pluginDownloads,
|
pluginDownloads,
|
||||||
info,
|
info,
|
||||||
pluginLists,
|
pluginChangeListener,
|
||||||
].filter(notNull);
|
].filter(notNull);
|
||||||
const globalCleanup = dispatchers
|
const globalCleanup = dispatchers
|
||||||
.map((dispatcher) => dispatcher(store, logger))
|
.map((dispatcher) => dispatcher(store, logger))
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Client from '../Client';
|
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
|
||||||
import {Store} from '../reducers';
|
|
||||||
import {pluginListsChanged} from '../reducers/pluginLists';
|
|
||||||
import {computePluginLists} from '../utils/pluginUtils';
|
|
||||||
import {sideEffect} from '../utils/sideEffect';
|
|
||||||
|
|
||||||
export default (store: Store, _logger: Logger) => {
|
|
||||||
const recomputePluginList = () => {
|
|
||||||
store.dispatch(
|
|
||||||
pluginListsChanged(
|
|
||||||
computePluginLists(
|
|
||||||
store.getState().connections,
|
|
||||||
store.getState().plugins,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let prevClient: null | Client = null;
|
|
||||||
|
|
||||||
sideEffect(
|
|
||||||
store,
|
|
||||||
{name: 'computePluginLists', throttleMs: 100, fireImmediately: true},
|
|
||||||
(state) => {
|
|
||||||
const {
|
|
||||||
activeClient,
|
|
||||||
activeDevice,
|
|
||||||
metroDevice,
|
|
||||||
enabledDevicePlugins,
|
|
||||||
enabledPlugins,
|
|
||||||
} = state.connections;
|
|
||||||
const {
|
|
||||||
bundledPlugins,
|
|
||||||
marketplacePlugins,
|
|
||||||
loadedPlugins,
|
|
||||||
devicePlugins,
|
|
||||||
disabledPlugins,
|
|
||||||
gatekeepedPlugins,
|
|
||||||
failedPlugins,
|
|
||||||
clientPlugins,
|
|
||||||
} = state.plugins;
|
|
||||||
return {
|
|
||||||
activeClient,
|
|
||||||
activeDevice,
|
|
||||||
metroDevice,
|
|
||||||
enabledDevicePlugins,
|
|
||||||
enabledPlugins,
|
|
||||||
bundledPlugins,
|
|
||||||
marketplacePlugins,
|
|
||||||
loadedPlugins,
|
|
||||||
devicePlugins,
|
|
||||||
disabledPlugins,
|
|
||||||
gatekeepedPlugins,
|
|
||||||
failedPlugins,
|
|
||||||
clientPlugins,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
(
|
|
||||||
{
|
|
||||||
activeClient,
|
|
||||||
activeDevice,
|
|
||||||
metroDevice,
|
|
||||||
enabledDevicePlugins,
|
|
||||||
enabledPlugins,
|
|
||||||
bundledPlugins,
|
|
||||||
marketplacePlugins,
|
|
||||||
loadedPlugins,
|
|
||||||
devicePlugins,
|
|
||||||
disabledPlugins,
|
|
||||||
gatekeepedPlugins,
|
|
||||||
failedPlugins,
|
|
||||||
clientPlugins,
|
|
||||||
},
|
|
||||||
store,
|
|
||||||
) => {
|
|
||||||
store.dispatch(
|
|
||||||
pluginListsChanged(
|
|
||||||
computePluginLists(
|
|
||||||
{
|
|
||||||
activeClient,
|
|
||||||
activeDevice,
|
|
||||||
metroDevice,
|
|
||||||
enabledDevicePlugins,
|
|
||||||
enabledPlugins,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bundledPlugins,
|
|
||||||
marketplacePlugins,
|
|
||||||
loadedPlugins,
|
|
||||||
devicePlugins,
|
|
||||||
disabledPlugins,
|
|
||||||
gatekeepedPlugins,
|
|
||||||
failedPlugins,
|
|
||||||
clientPlugins,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (activeClient !== prevClient) {
|
|
||||||
if (prevClient) {
|
|
||||||
prevClient.off('plugins-change', recomputePluginList);
|
|
||||||
}
|
|
||||||
prevClient = activeClient;
|
|
||||||
if (prevClient) {
|
|
||||||
prevClient.on('plugins-change', recomputePluginList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
40
desktop/app/src/dispatcher/pluginsChangeListener.tsx
Normal file
40
desktop/app/src/dispatcher/pluginsChangeListener.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Client from '../Client';
|
||||||
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
|
import {Store} from '../reducers';
|
||||||
|
import {appPluginListChanged} from '../reducers/connections';
|
||||||
|
import {getActiveClient} from '../selectors/connections';
|
||||||
|
import {sideEffect} from '../utils/sideEffect';
|
||||||
|
|
||||||
|
export default (store: Store, _logger: Logger) => {
|
||||||
|
let prevClient: null | Client = null;
|
||||||
|
|
||||||
|
const onActiveAppPluginListChanged = () => {
|
||||||
|
store.dispatch(appPluginListChanged());
|
||||||
|
};
|
||||||
|
|
||||||
|
sideEffect(
|
||||||
|
store,
|
||||||
|
{name: 'pluginsChangeListener', throttleMs: 100, fireImmediately: true},
|
||||||
|
getActiveClient,
|
||||||
|
(activeClient, _store) => {
|
||||||
|
if (activeClient !== prevClient) {
|
||||||
|
if (prevClient) {
|
||||||
|
prevClient.off('plugins-change', onActiveAppPluginListChanged);
|
||||||
|
}
|
||||||
|
prevClient = activeClient;
|
||||||
|
if (prevClient) {
|
||||||
|
prevClient.on('plugins-change', onActiveAppPluginListChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -78,9 +78,7 @@ type StateV2 = {
|
|||||||
}>;
|
}>;
|
||||||
deepLinkPayload: unknown;
|
deepLinkPayload: unknown;
|
||||||
staticView: StaticView;
|
staticView: StaticView;
|
||||||
activeClient: Client | null;
|
selectedAppPluginListRevision: number;
|
||||||
activeDevice: BaseDevice | null;
|
|
||||||
metroDevice: MetroDevice | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateV1 = Omit<StateV2, 'enabledPlugins' | 'enabledDevicePlugins'> & {
|
type StateV1 = Omit<StateV2, 'enabledPlugins' | 'enabledDevicePlugins'> & {
|
||||||
@@ -179,6 +177,9 @@ export type Action =
|
|||||||
type: 'SELECT_CLIENT';
|
type: 'SELECT_CLIENT';
|
||||||
payload: string | null;
|
payload: string | null;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'APP_PLUGIN_LIST_CHANGED';
|
||||||
|
}
|
||||||
| RegisterPluginAction;
|
| RegisterPluginAction;
|
||||||
|
|
||||||
const DEFAULT_PLUGIN = 'DeviceLogs';
|
const DEFAULT_PLUGIN = 'DeviceLogs';
|
||||||
@@ -204,9 +205,7 @@ const INITAL_STATE: State = {
|
|||||||
uninitializedClients: [],
|
uninitializedClients: [],
|
||||||
deepLinkPayload: null,
|
deepLinkPayload: null,
|
||||||
staticView: WelcomeScreenStaticView,
|
staticView: WelcomeScreenStaticView,
|
||||||
activeClient: null,
|
selectedAppPluginListRevision: 0,
|
||||||
activeDevice: null,
|
|
||||||
metroDevice: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state: State = INITAL_STATE, action: Actions): State => {
|
export default (state: State = INITAL_STATE, action: Actions): State => {
|
||||||
@@ -469,6 +468,11 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
|
|||||||
draft.enabledDevicePlugins.delete(pluginId);
|
draft.enabledDevicePlugins.delete(pluginId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case 'APP_PLUGIN_LIST_CHANGED': {
|
||||||
|
return produce(state, (draft) => {
|
||||||
|
draft.selectedAppPluginListRevision++;
|
||||||
|
});
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -544,6 +548,10 @@ export const setPluginDisabled = (pluginId: string, appId: string): Action => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const appPluginListChanged = (): Action => ({
|
||||||
|
type: 'APP_PLUGIN_LIST_CHANGED',
|
||||||
|
});
|
||||||
|
|
||||||
export function getAvailableClients(
|
export function getAvailableClients(
|
||||||
device: null | undefined | BaseDevice,
|
device: null | undefined | BaseDevice,
|
||||||
clients: Client[],
|
clients: Client[],
|
||||||
@@ -568,10 +576,10 @@ function getBestAvailableClient(
|
|||||||
device: BaseDevice | null | undefined,
|
device: BaseDevice | null | undefined,
|
||||||
clients: Client[],
|
clients: Client[],
|
||||||
preferredClient: string | null,
|
preferredClient: string | null,
|
||||||
): Client | undefined {
|
): Client | null {
|
||||||
const availableClients = getAvailableClients(device, clients);
|
const availableClients = getAvailableClients(device, clients);
|
||||||
if (availableClients.length === 0) {
|
if (availableClients.length === 0) {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
getClientById(availableClients, preferredClient) ||
|
getClientById(availableClients, preferredClient) ||
|
||||||
@@ -625,29 +633,12 @@ function updateSelection(state: Readonly<State>): State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select client based on device
|
// Select client based on device
|
||||||
updates.activeClient = getBestAvailableClient(
|
const client = getBestAvailableClient(
|
||||||
device,
|
device,
|
||||||
state.clients,
|
state.clients,
|
||||||
state.selectedApp || state.userPreferredApp,
|
state.selectedApp || state.userPreferredApp,
|
||||||
);
|
);
|
||||||
updates.selectedApp = updates.activeClient ? updates.activeClient.id : null;
|
updates.selectedApp = client ? client.id : null;
|
||||||
|
|
||||||
updates.metroDevice =
|
|
||||||
(state.devices?.find(
|
|
||||||
(device) => device.os === 'Metro' && !device.isArchived,
|
|
||||||
) as MetroDevice) ?? null;
|
|
||||||
|
|
||||||
updates.activeClient =
|
|
||||||
state.clients.find(
|
|
||||||
(c) => c.id === (updates.selectedApp || state.userPreferredApp),
|
|
||||||
) ?? null;
|
|
||||||
|
|
||||||
// if the selected device is Metro, we want to keep the owner of the selected App as active device if possible
|
|
||||||
updates.activeDevice = findBestDevice(
|
|
||||||
state,
|
|
||||||
updates.activeClient,
|
|
||||||
updates.metroDevice,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// Try the preferred plugin first
|
// Try the preferred plugin first
|
||||||
@@ -667,31 +658,6 @@ function updateSelection(state: Readonly<State>): State {
|
|||||||
return {...state, ...updates};
|
return {...state, ...updates};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findBestDevice(
|
|
||||||
state: State,
|
|
||||||
client: Client | null,
|
|
||||||
metroDevice: BaseDevice | null,
|
|
||||||
): BaseDevice | null {
|
|
||||||
// if not Metro device, use the selected device as metro device
|
|
||||||
const selected = state.selectedDevice ?? null;
|
|
||||||
if (selected !== metroDevice) {
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
// if there is an active app, use device owning the app
|
|
||||||
if (client) {
|
|
||||||
return client.deviceSync;
|
|
||||||
}
|
|
||||||
// if no active app, use the preferred device
|
|
||||||
if (state.userPreferredDevice) {
|
|
||||||
return (
|
|
||||||
state.devices.find(
|
|
||||||
(device) => device.title === state.userPreferredDevice,
|
|
||||||
) ?? selected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSelectedPluginKey(state: State): string | undefined {
|
export function getSelectedPluginKey(state: State): string | undefined {
|
||||||
return state.selectedPlugin
|
return state.selectedPlugin
|
||||||
? getPluginKey(
|
? getPluginKey(
|
||||||
|
|||||||
@@ -64,10 +64,6 @@ import usageTracking, {
|
|||||||
Action as TrackingAction,
|
Action as TrackingAction,
|
||||||
State as TrackingState,
|
State as TrackingState,
|
||||||
} from './usageTracking';
|
} from './usageTracking';
|
||||||
import pluginLists, {
|
|
||||||
State as PluginListsState,
|
|
||||||
Action as PluginListsAction,
|
|
||||||
} from './pluginLists';
|
|
||||||
import user, {State as UserState, Action as UserAction} from './user';
|
import user, {State as UserState, Action as UserAction} from './user';
|
||||||
import JsonFileStorage from '../utils/jsonFileReduxPersistStorage';
|
import JsonFileStorage from '../utils/jsonFileReduxPersistStorage';
|
||||||
import LauncherSettingsStorage from '../utils/launcherSettingsStorage';
|
import LauncherSettingsStorage from '../utils/launcherSettingsStorage';
|
||||||
@@ -97,7 +93,6 @@ export type Actions =
|
|||||||
| HealthcheckAction
|
| HealthcheckAction
|
||||||
| TrackingAction
|
| TrackingAction
|
||||||
| PluginDownloadsAction
|
| PluginDownloadsAction
|
||||||
| PluginListsAction
|
|
||||||
| {type: 'INIT'};
|
| {type: 'INIT'};
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
@@ -115,7 +110,6 @@ export type State = {
|
|||||||
healthchecks: HealthcheckState & PersistPartial;
|
healthchecks: HealthcheckState & PersistPartial;
|
||||||
usageTracking: TrackingState;
|
usageTracking: TrackingState;
|
||||||
pluginDownloads: PluginDownloadsState;
|
pluginDownloads: PluginDownloadsState;
|
||||||
pluginLists: PluginListsState;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Store = ReduxStore<State, Actions>;
|
export type Store = ReduxStore<State, Actions>;
|
||||||
@@ -218,6 +212,5 @@ export function createRootReducer() {
|
|||||||
),
|
),
|
||||||
usageTracking,
|
usageTracking,
|
||||||
pluginDownloads,
|
pluginDownloads,
|
||||||
pluginLists,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
PluginDetails,
|
|
||||||
DownloadablePluginDetails,
|
|
||||||
BundledPluginDetails,
|
|
||||||
} from 'flipper-plugin-lib';
|
|
||||||
import {Actions} from '.';
|
|
||||||
import {
|
|
||||||
DevicePluginDefinition,
|
|
||||||
ClientPluginDefinition,
|
|
||||||
PluginDefinition,
|
|
||||||
} from '../plugin';
|
|
||||||
import produce from 'immer';
|
|
||||||
|
|
||||||
export type State = {
|
|
||||||
devicePlugins: DevicePluginDefinition[];
|
|
||||||
metroPlugins: DevicePluginDefinition[];
|
|
||||||
enabledPlugins: ClientPluginDefinition[];
|
|
||||||
disabledPlugins: PluginDefinition[];
|
|
||||||
unavailablePlugins: [plugin: PluginDetails, reason: string][];
|
|
||||||
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const INITIAL_STATE: State = {
|
|
||||||
devicePlugins: [],
|
|
||||||
metroPlugins: [],
|
|
||||||
enabledPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
unavailablePlugins: [],
|
|
||||||
downloadablePlugins: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action = {
|
|
||||||
type: 'PLUGIN_LISTS_CHANGED';
|
|
||||||
payload: State;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reducer(
|
|
||||||
state: State | undefined = INITIAL_STATE,
|
|
||||||
action: Actions,
|
|
||||||
): State {
|
|
||||||
if (action.type === 'PLUGIN_LISTS_CHANGED') {
|
|
||||||
const payload = action.payload;
|
|
||||||
return produce(state, (draft) => {
|
|
||||||
draft.devicePlugins = payload.devicePlugins;
|
|
||||||
draft.metroPlugins = payload.metroPlugins;
|
|
||||||
draft.enabledPlugins = payload.enabledPlugins;
|
|
||||||
draft.disabledPlugins = payload.disabledPlugins;
|
|
||||||
draft.unavailablePlugins = payload.unavailablePlugins;
|
|
||||||
draft.downloadablePlugins = payload.downloadablePlugins;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pluginListsChanged = (payload: State): Action => ({
|
|
||||||
type: 'PLUGIN_LISTS_CHANGED',
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
@@ -17,10 +17,9 @@ import {addStatusMessage, removeStatusMessage} from './application';
|
|||||||
import constants from '../fb-stubs/constants';
|
import constants from '../fb-stubs/constants';
|
||||||
import {getInstance} from '../fb-stubs/Logger';
|
import {getInstance} from '../fb-stubs/Logger';
|
||||||
import {logPlatformSuccessRate} from '../utils/metrics';
|
import {logPlatformSuccessRate} from '../utils/metrics';
|
||||||
import {getExportablePlugins} from '../utils/pluginUtils';
|
|
||||||
export const SUPPORT_FORM_PREFIX = 'support-form-v2';
|
export const SUPPORT_FORM_PREFIX = 'support-form-v2';
|
||||||
import Client from '../Client';
|
import {OS} from '../devices/BaseDevice';
|
||||||
import BaseDevice, {OS} from '../devices/BaseDevice';
|
import {getExportablePlugins} from '../selectors/connections';
|
||||||
|
|
||||||
const {DEFAULT_SUPPORT_GROUP} = constants;
|
const {DEFAULT_SUPPORT_GROUP} = constants;
|
||||||
|
|
||||||
@@ -193,11 +192,7 @@ export class Group {
|
|||||||
selectedGroup: this,
|
selectedGroup: this,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const pluginsList = getExportablePlugins(
|
const pluginsList = getExportablePlugins(store.getState());
|
||||||
store.getState(),
|
|
||||||
store.getState().connections.selectedDevice ?? undefined,
|
|
||||||
selectedClient,
|
|
||||||
);
|
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
setSelectedPlugins(
|
setSelectedPlugins(
|
||||||
@@ -220,10 +215,8 @@ export class Group {
|
|||||||
|
|
||||||
getWarningMessage(
|
getWarningMessage(
|
||||||
state: Parameters<typeof getExportablePlugins>[0],
|
state: Parameters<typeof getExportablePlugins>[0],
|
||||||
device: BaseDevice | undefined,
|
|
||||||
client: Client,
|
|
||||||
): string | null {
|
): string | null {
|
||||||
const activePersistentPlugins = getExportablePlugins(state, device, client);
|
const activePersistentPlugins = getExportablePlugins(state);
|
||||||
const emptyPlugins: Array<string> = [];
|
const emptyPlugins: Array<string> = [];
|
||||||
for (const plugin of this.requiredPlugins) {
|
for (const plugin of this.requiredPlugins) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
|
|||||||
import {Layout, Link, styled} from '../../ui';
|
import {Layout, Link, styled} from '../../ui';
|
||||||
import {theme, useValue} from 'flipper-plugin';
|
import {theme, useValue} from 'flipper-plugin';
|
||||||
import {AppSelector} from './AppSelector';
|
import {AppSelector} from './AppSelector';
|
||||||
import {useStore} from '../../utils/useStore';
|
|
||||||
import {PluginList} from './PluginList';
|
import {PluginList} from './PluginList';
|
||||||
import ScreenCaptureButtons from '../../chrome/ScreenCaptureButtons';
|
import ScreenCaptureButtons from '../../chrome/ScreenCaptureButtons';
|
||||||
import MetroButton from '../../chrome/MetroButton';
|
import MetroButton from '../../chrome/MetroButton';
|
||||||
@@ -21,6 +20,12 @@ import {BookmarkSection} from './BookmarkSection';
|
|||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
import {ExclamationCircleOutlined, FieldTimeOutlined} from '@ant-design/icons';
|
import {ExclamationCircleOutlined, FieldTimeOutlined} from '@ant-design/icons';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import {
|
||||||
|
getActiveClient,
|
||||||
|
getActiveDevice,
|
||||||
|
getMetroDevice,
|
||||||
|
} from '../../selectors/connections';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
|
|
||||||
@@ -36,11 +41,9 @@ const appTooltip = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function AppInspect() {
|
export function AppInspect() {
|
||||||
const connections = useStore((state) => state.connections);
|
const metroDevice = useSelector(getMetroDevice);
|
||||||
|
const client = useSelector(getActiveClient);
|
||||||
const metroDevice = connections.metroDevice;
|
const activeDevice = useSelector(getActiveDevice);
|
||||||
const client = connections.activeClient;
|
|
||||||
const activeDevice = connections.activeDevice;
|
|
||||||
const isDeviceConnected = useValue(activeDevice?.connected, false);
|
const isDeviceConnected = useValue(activeDevice?.connected, false);
|
||||||
const isAppConnected = useValue(client?.connected, false);
|
const isAppConnected = useValue(client?.connected, false);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import {
|
|||||||
import {BundledPluginDetails} from 'flipper-plugin-lib';
|
import {BundledPluginDetails} from 'flipper-plugin-lib';
|
||||||
import {reportUsage} from '../../utils/metrics';
|
import {reportUsage} from '../../utils/metrics';
|
||||||
import ConnectivityStatus from './fb-stubs/ConnectivityStatus';
|
import ConnectivityStatus from './fb-stubs/ConnectivityStatus';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import {getPluginLists} from '../../selectors/connections';
|
||||||
|
|
||||||
const {SubMenu} = Menu;
|
const {SubMenu} = Menu;
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
@@ -55,7 +57,7 @@ export const PluginList = memo(function PluginList({
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const connections = useStore((state) => state.connections);
|
const connections = useStore((state) => state.connections);
|
||||||
const plugins = useStore((state) => state.plugins);
|
const plugins = useStore((state) => state.plugins);
|
||||||
const pluginLists = useStore((state) => state.pluginLists);
|
const pluginLists = useSelector(getPluginLists);
|
||||||
const downloads = useStore((state) => state.pluginDownloads);
|
const downloads = useStore((state) => state.pluginDownloads);
|
||||||
const isConnected = useValue(activeDevice?.connected, false);
|
const isConnected = useValue(activeDevice?.connected, false);
|
||||||
const metroConnected = useValue(metroDevice?.connected, false);
|
const metroConnected = useValue(metroDevice?.connected, false);
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ import {switchPlugin} from '../../../reducers/pluginManager';
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
import * as LogsPluginModule from '../../../../../plugins/public/logs/index';
|
import * as LogsPluginModule from '../../../../../plugins/public/logs/index';
|
||||||
import {createMockDownloadablePluginDetails} from '../../../utils/testUtils';
|
import {createMockDownloadablePluginDetails} from '../../../utils/testUtils';
|
||||||
import {computePluginLists} from '../../../utils/pluginUtils';
|
import {
|
||||||
|
getActiveClient,
|
||||||
|
getActiveDevice,
|
||||||
|
getMetroDevice,
|
||||||
|
getPluginLists,
|
||||||
|
} from '../../../selectors/connections';
|
||||||
|
|
||||||
const createMockPluginDetails = TestUtils.createMockPluginDetails;
|
const createMockPluginDetails = TestUtils.createMockPluginDetails;
|
||||||
|
|
||||||
@@ -39,32 +44,29 @@ const logsPlugin = new _SandyPluginDefinition(
|
|||||||
|
|
||||||
class TestPlugin extends FlipperPlugin<any, any, any> {}
|
class TestPlugin extends FlipperPlugin<any, any, any> {}
|
||||||
|
|
||||||
describe('basic findBestDevice', () => {
|
describe('basic getActiveDevice', () => {
|
||||||
let flipper: MockFlipperResult;
|
let flipper: MockFlipperResult;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
flipper = await createMockFlipperWithPlugin(TestPlugin);
|
flipper = await createMockFlipperWithPlugin(TestPlugin);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findBestDevice prefers selected device', () => {
|
test('getActiveDevice prefers selected device', () => {
|
||||||
const {device} = flipper;
|
const {device, store} = flipper;
|
||||||
const {connections} = flipper.store.getState();
|
expect(getActiveDevice(store.getState())).toBe(device);
|
||||||
expect(connections.activeDevice).toBe(device);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findBestDevice picks device of current client', () => {
|
test('getActiveDevice picks device of current client', () => {
|
||||||
const {device} = flipper;
|
const {device, store} = flipper;
|
||||||
const {connections} = flipper.store.getState();
|
expect(getActiveDevice(store.getState())).toBe(device);
|
||||||
expect(connections.activeDevice).toBe(device);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findBestDevice picks preferred device if no client and device', () => {
|
test('getActiveDevice picks preferred device if no client and device', () => {
|
||||||
const {device} = flipper;
|
const {device, store} = flipper;
|
||||||
const {connections} = flipper.store.getState();
|
expect(getActiveDevice(store.getState())).toBe(device);
|
||||||
expect(connections.activeDevice).toBe(device);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('basic findBestDevice with metro present', () => {
|
describe('basic getActiveDevice with metro present', () => {
|
||||||
let flipper: MockFlipperResult;
|
let flipper: MockFlipperResult;
|
||||||
let metro: MetroDevice;
|
let metro: MetroDevice;
|
||||||
let testDevice: BaseDevice;
|
let testDevice: BaseDevice;
|
||||||
@@ -77,7 +79,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
testDevice = flipper.device;
|
testDevice = flipper.device;
|
||||||
// flipper.store.dispatch(registerPlugins([LogsPlugin]))
|
// flipper.store.dispatch(registerPlugins([LogsPlugin]))
|
||||||
await registerMetroDevice(undefined, flipper.store, flipper.logger);
|
await registerMetroDevice(undefined, flipper.store, flipper.logger);
|
||||||
metro = flipper.store.getState().connections.metroDevice!;
|
metro = getMetroDevice(flipper.store.getState())!;
|
||||||
metro.supportsPlugin = (p) => {
|
metro.supportsPlugin = (p) => {
|
||||||
return p.id !== 'unsupportedDevicePlugin';
|
return p.id !== 'unsupportedDevicePlugin';
|
||||||
};
|
};
|
||||||
@@ -88,7 +90,8 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('correct base selection state', () => {
|
test('correct base selection state', () => {
|
||||||
const {connections} = flipper.store.getState();
|
const state = flipper.store.getState();
|
||||||
|
const {connections} = state;
|
||||||
expect(connections).toMatchObject({
|
expect(connections).toMatchObject({
|
||||||
devices: [testDevice, metro],
|
devices: [testDevice, metro],
|
||||||
selectedDevice: testDevice,
|
selectedDevice: testDevice,
|
||||||
@@ -97,10 +100,11 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
userPreferredPlugin: 'DeviceLogs',
|
userPreferredPlugin: 'DeviceLogs',
|
||||||
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
|
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
|
||||||
});
|
});
|
||||||
expect(connections.activeClient).toBe(flipper.client);
|
expect(getActiveClient(state)).toBe(flipper.client);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('selecting Metro Logs works but keeps normal device preferred', () => {
|
test('selecting Metro Logs works but keeps normal device preferred', () => {
|
||||||
|
expect(getActiveClient(flipper.store.getState())).toBe(flipper.client);
|
||||||
flipper.store.dispatch(
|
flipper.store.dispatch(
|
||||||
selectPlugin({
|
selectPlugin({
|
||||||
selectedPlugin: logsPlugin.id,
|
selectedPlugin: logsPlugin.id,
|
||||||
@@ -118,16 +122,16 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
userPreferredPlugin: 'DeviceLogs',
|
userPreferredPlugin: 'DeviceLogs',
|
||||||
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
|
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
|
||||||
});
|
});
|
||||||
const {connections} = flipper.store.getState();
|
const state = flipper.store.getState();
|
||||||
// find best device is still metro
|
// find best device is still metro
|
||||||
expect(connections.activeDevice).toBe(testDevice);
|
expect(getActiveDevice(state)).toBe(testDevice);
|
||||||
// find best client still returns app
|
// find best client still returns app
|
||||||
expect(connections.activeClient).toBe(flipper.client);
|
expect(getActiveClient(state)).toBe(flipper.client);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('computePluginLists', () => {
|
test('computePluginLists', () => {
|
||||||
const state = flipper.store.getState();
|
const state = flipper.store.getState();
|
||||||
expect(computePluginLists(state.connections, state.plugins)).toEqual({
|
expect(getPluginLists(state)).toEqual({
|
||||||
downloadablePlugins: [],
|
downloadablePlugins: [],
|
||||||
devicePlugins: [logsPlugin],
|
devicePlugins: [logsPlugin],
|
||||||
metroPlugins: [logsPlugin],
|
metroPlugins: [logsPlugin],
|
||||||
@@ -231,7 +235,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
let state = flipper.store.getState();
|
let state = flipper.store.getState();
|
||||||
const pluginLists = computePluginLists(state.connections, state.plugins);
|
const pluginLists = getPluginLists(state);
|
||||||
expect(pluginLists).toEqual({
|
expect(pluginLists).toEqual({
|
||||||
devicePlugins: [logsPlugin],
|
devicePlugins: [logsPlugin],
|
||||||
metroPlugins: [logsPlugin],
|
metroPlugins: [logsPlugin],
|
||||||
@@ -265,7 +269,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
state = flipper.store.getState();
|
state = flipper.store.getState();
|
||||||
expect(computePluginLists(state.connections, state.plugins)).toMatchObject({
|
expect(getPluginLists(state)).toMatchObject({
|
||||||
enabledPlugins: [plugin2],
|
enabledPlugins: [plugin2],
|
||||||
disabledPlugins: [plugin1],
|
disabledPlugins: [plugin1],
|
||||||
});
|
});
|
||||||
|
|||||||
137
desktop/app/src/selectors/connections.tsx
Normal file
137
desktop/app/src/selectors/connections.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
PluginDetails,
|
||||||
|
DownloadablePluginDetails,
|
||||||
|
BundledPluginDetails,
|
||||||
|
} from 'flipper-plugin-lib';
|
||||||
|
import MetroDevice from '../devices/MetroDevice';
|
||||||
|
import {
|
||||||
|
DevicePluginDefinition,
|
||||||
|
ClientPluginDefinition,
|
||||||
|
PluginDefinition,
|
||||||
|
} from '../plugin';
|
||||||
|
import {State} from '../reducers';
|
||||||
|
import {
|
||||||
|
computePluginLists,
|
||||||
|
computeExportablePlugins,
|
||||||
|
} from '../utils/pluginUtils';
|
||||||
|
import createSelector from './createSelector';
|
||||||
|
|
||||||
|
export type PluginLists = {
|
||||||
|
devicePlugins: DevicePluginDefinition[];
|
||||||
|
metroPlugins: DevicePluginDefinition[];
|
||||||
|
enabledPlugins: ClientPluginDefinition[];
|
||||||
|
disabledPlugins: PluginDefinition[];
|
||||||
|
unavailablePlugins: [plugin: PluginDetails, reason: string][];
|
||||||
|
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedApp = (state: State) =>
|
||||||
|
state.connections.selectedApp || state.connections.userPreferredApp;
|
||||||
|
const getSelectedDevice = (state: State) => state.connections.selectedDevice;
|
||||||
|
const getUserPreferredDevice = (state: State) =>
|
||||||
|
state.connections.userPreferredDevice;
|
||||||
|
const getClients = (state: State) => state.connections.clients;
|
||||||
|
const getDevices = (state: State) => state.connections.devices;
|
||||||
|
|
||||||
|
export const getActiveClient = createSelector(
|
||||||
|
getSelectedApp,
|
||||||
|
getClients,
|
||||||
|
(selectedApp, clients) => {
|
||||||
|
return clients.find((c) => c.id === selectedApp) || null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getMetroDevice = createSelector(getDevices, (devices) => {
|
||||||
|
return (
|
||||||
|
(devices.find(
|
||||||
|
(device) => device.os === 'Metro' && !device.isArchived,
|
||||||
|
) as MetroDevice) ?? null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getActiveDevice = createSelector(
|
||||||
|
getSelectedDevice,
|
||||||
|
getUserPreferredDevice,
|
||||||
|
getDevices,
|
||||||
|
getActiveClient,
|
||||||
|
getMetroDevice,
|
||||||
|
(selectedDevice, userPreferredDevice, devices, client, metroDevice) => {
|
||||||
|
// if not Metro device, use the selected device as metro device
|
||||||
|
if (selectedDevice !== metroDevice) {
|
||||||
|
return selectedDevice;
|
||||||
|
}
|
||||||
|
// if there is an active app, use device owning the app
|
||||||
|
if (client) {
|
||||||
|
return client.deviceSync;
|
||||||
|
}
|
||||||
|
// if no active app, use the preferred device
|
||||||
|
if (userPreferredDevice) {
|
||||||
|
return (
|
||||||
|
devices.find((device) => device.title === userPreferredDevice) ??
|
||||||
|
selectedDevice
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return selectedDevice;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getPluginLists = createSelector(
|
||||||
|
({
|
||||||
|
connections: {
|
||||||
|
enabledDevicePlugins,
|
||||||
|
enabledPlugins,
|
||||||
|
selectedAppPluginListRevision, // used only to invalidate cache
|
||||||
|
},
|
||||||
|
}: State) => ({
|
||||||
|
enabledDevicePlugins,
|
||||||
|
enabledPlugins,
|
||||||
|
selectedAppPluginListRevision,
|
||||||
|
}),
|
||||||
|
({
|
||||||
|
plugins: {
|
||||||
|
clientPlugins,
|
||||||
|
devicePlugins,
|
||||||
|
bundledPlugins,
|
||||||
|
marketplacePlugins,
|
||||||
|
loadedPlugins,
|
||||||
|
disabledPlugins,
|
||||||
|
gatekeepedPlugins,
|
||||||
|
failedPlugins,
|
||||||
|
},
|
||||||
|
}: State) => ({
|
||||||
|
clientPlugins,
|
||||||
|
devicePlugins,
|
||||||
|
bundledPlugins,
|
||||||
|
marketplacePlugins,
|
||||||
|
loadedPlugins,
|
||||||
|
disabledPlugins,
|
||||||
|
gatekeepedPlugins,
|
||||||
|
failedPlugins,
|
||||||
|
}),
|
||||||
|
getActiveDevice,
|
||||||
|
getMetroDevice,
|
||||||
|
getActiveClient,
|
||||||
|
computePluginLists,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getExportablePlugins = createSelector(
|
||||||
|
({plugins, connections, pluginStates, pluginMessageQueue}: State) => ({
|
||||||
|
plugins,
|
||||||
|
connections,
|
||||||
|
pluginStates,
|
||||||
|
pluginMessageQueue,
|
||||||
|
}),
|
||||||
|
getActiveDevice,
|
||||||
|
getActiveClient,
|
||||||
|
getPluginLists,
|
||||||
|
computeExportablePlugins,
|
||||||
|
);
|
||||||
18
desktop/app/src/selectors/createSelector.tsx
Normal file
18
desktop/app/src/selectors/createSelector.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {shallowEqual} from 'react-redux';
|
||||||
|
import {createSelectorCreator, defaultMemoize} from 'reselect';
|
||||||
|
|
||||||
|
export const createSelector = createSelectorCreator(
|
||||||
|
defaultMemoize,
|
||||||
|
shallowEqual,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default createSelector;
|
||||||
@@ -7,9 +7,10 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getExportablePlugins, getPluginKey} from '../pluginUtils';
|
import {getPluginKey} from '../pluginUtils';
|
||||||
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
||||||
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
|
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
|
||||||
|
import {getExportablePlugins} from '../../selectors/connections';
|
||||||
|
|
||||||
function createMockFlipperPluginWithDefaultPersistedState(id: string) {
|
function createMockFlipperPluginWithDefaultPersistedState(id: string) {
|
||||||
return class MockFlipperPluginWithDefaultPersistedState extends FlipperPlugin<
|
return class MockFlipperPluginWithDefaultPersistedState extends FlipperPlugin<
|
||||||
@@ -89,7 +90,7 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu
|
|||||||
[getPluginKey(client.id, device, 'ClientPlugin4')]: {msg: 'ClientPlugin2'},
|
[getPluginKey(client.id, device, 'ClientPlugin4')]: {msg: 'ClientPlugin2'},
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = getExportablePlugins(state, device, client);
|
const list = getExportablePlugins(state);
|
||||||
expect(list).toEqual([
|
expect(list).toEqual([
|
||||||
{
|
{
|
||||||
id: 'ClientPlugin1',
|
id: 'ClientPlugin1',
|
||||||
@@ -130,7 +131,7 @@ test('getActivePersistentPlugins, where the plugins not in pluginState or queue
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = getExportablePlugins(store.getState(), device, client);
|
const list = getExportablePlugins(store.getState());
|
||||||
expect(list).toEqual([
|
expect(list).toEqual([
|
||||||
{
|
{
|
||||||
id: 'ClientPlugin2', // has state
|
id: 'ClientPlugin2', // has state
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type {
|
|||||||
PluginDetails,
|
PluginDetails,
|
||||||
} from 'flipper-plugin-lib';
|
} from 'flipper-plugin-lib';
|
||||||
import {getLatestCompatibleVersionOfEachPlugin} from '../dispatcher/plugins';
|
import {getLatestCompatibleVersionOfEachPlugin} from '../dispatcher/plugins';
|
||||||
|
import {PluginLists} from '../selectors/connections';
|
||||||
|
|
||||||
export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation plugin is enabled always, to make sure the navigation features works
|
export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation plugin is enabled always, to make sure the navigation features works
|
||||||
|
|
||||||
@@ -75,15 +76,15 @@ export function getPersistedState<PersistedState>(
|
|||||||
return persistedState;
|
return persistedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExportablePlugins(
|
export function computeExportablePlugins(
|
||||||
state: Pick<
|
state: Pick<
|
||||||
State,
|
State,
|
||||||
'plugins' | 'connections' | 'pluginStates' | 'pluginMessageQueue'
|
'plugins' | 'connections' | 'pluginStates' | 'pluginMessageQueue'
|
||||||
>,
|
>,
|
||||||
device: BaseDevice | undefined | null,
|
device: BaseDevice | null,
|
||||||
client?: Client,
|
client: Client | null,
|
||||||
|
availablePlugins: PluginLists,
|
||||||
): {id: string; label: string}[] {
|
): {id: string; label: string}[] {
|
||||||
const availablePlugins = computePluginLists(state.connections, state.plugins);
|
|
||||||
return [
|
return [
|
||||||
...availablePlugins.devicePlugins.filter((plugin) => {
|
...availablePlugins.devicePlugins.filter((plugin) => {
|
||||||
return isExportablePlugin(state, device, client, plugin);
|
return isExportablePlugin(state, device, client, plugin);
|
||||||
@@ -102,8 +103,8 @@ function isExportablePlugin(
|
|||||||
pluginStates,
|
pluginStates,
|
||||||
pluginMessageQueue,
|
pluginMessageQueue,
|
||||||
}: Pick<State, 'pluginStates' | 'pluginMessageQueue'>,
|
}: Pick<State, 'pluginStates' | 'pluginMessageQueue'>,
|
||||||
device: BaseDevice | undefined | null,
|
device: BaseDevice | null,
|
||||||
client: Client | undefined,
|
client: Client | null,
|
||||||
plugin: PluginDefinition,
|
plugin: PluginDefinition,
|
||||||
): boolean {
|
): boolean {
|
||||||
// can generate an export when requested
|
// can generate an export when requested
|
||||||
@@ -174,11 +175,7 @@ export function getPluginTooltip(details: PluginDetails): string {
|
|||||||
export function computePluginLists(
|
export function computePluginLists(
|
||||||
connections: Pick<
|
connections: Pick<
|
||||||
State['connections'],
|
State['connections'],
|
||||||
| 'activeDevice'
|
'enabledDevicePlugins' | 'enabledPlugins'
|
||||||
| 'activeClient'
|
|
||||||
| 'metroDevice'
|
|
||||||
| 'enabledDevicePlugins'
|
|
||||||
| 'enabledPlugins'
|
|
||||||
>,
|
>,
|
||||||
plugins: Pick<
|
plugins: Pick<
|
||||||
State['plugins'],
|
State['plugins'],
|
||||||
@@ -191,6 +188,9 @@ export function computePluginLists(
|
|||||||
| 'failedPlugins'
|
| 'failedPlugins'
|
||||||
| 'clientPlugins'
|
| 'clientPlugins'
|
||||||
>,
|
>,
|
||||||
|
device: BaseDevice | null,
|
||||||
|
metroDevice: BaseDevice | null,
|
||||||
|
client: Client | null,
|
||||||
): {
|
): {
|
||||||
devicePlugins: DevicePluginDefinition[];
|
devicePlugins: DevicePluginDefinition[];
|
||||||
metroPlugins: DevicePluginDefinition[];
|
metroPlugins: DevicePluginDefinition[];
|
||||||
@@ -199,9 +199,6 @@ export function computePluginLists(
|
|||||||
unavailablePlugins: [plugin: PluginDetails, reason: string][];
|
unavailablePlugins: [plugin: PluginDetails, reason: string][];
|
||||||
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
|
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
|
||||||
} {
|
} {
|
||||||
const device = connections.activeDevice;
|
|
||||||
const client = connections.activeClient;
|
|
||||||
const metroDevice = connections.metroDevice;
|
|
||||||
const enabledDevicePluginsState = connections.enabledDevicePlugins;
|
const enabledDevicePluginsState = connections.enabledDevicePlugins;
|
||||||
const enabledPluginsState = connections.enabledPlugins;
|
const enabledPluginsState = connections.enabledPlugins;
|
||||||
const uninstalledMarketplacePlugins = getLatestCompatibleVersionOfEachPlugin([
|
const uninstalledMarketplacePlugins = getLatestCompatibleVersionOfEachPlugin([
|
||||||
|
|||||||
@@ -12086,6 +12086,11 @@ require-main-filename@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||||
|
|
||||||
|
reselect@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||||
|
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||||
|
|
||||||
resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1:
|
resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
|||||||
Reference in New Issue
Block a user