Refactor plugin lists computations
Summary: This is purely refactoring change. Before that we computed plugin lists in-place in PluginList component. Now we will be re-computing them as side effect and will keep computed lists in redux. This makes it easier to re-use plugin lists in other places outside of PluginList component, e.g. in the upcoming Marketplace UI. Reviewed By: mweststrate Differential Revision: D29161719 fbshipit-source-id: 5cb06d4d8a553aa856101c78b2311fbc078c6bd7
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0d6262aa5e
commit
ac9ef7620a
@@ -2,6 +2,22 @@
|
|||||||
|
|
||||||
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 {
|
||||||
@@ -36,6 +52,7 @@ Object {
|
|||||||
"TestPlugin",
|
"TestPlugin",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"metroDevice": null,
|
||||||
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
|
||||||
"selectedDevice": Object {
|
"selectedDevice": Object {
|
||||||
"deviceType": "physical",
|
"deviceType": "physical",
|
||||||
|
|||||||
@@ -23,6 +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 {Logger} from '../fb-interfaces/Logger';
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
import {Store} from '../reducers/index';
|
import {Store} from '../reducers/index';
|
||||||
@@ -51,6 +52,7 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
|
|||||||
pluginMarketplace,
|
pluginMarketplace,
|
||||||
pluginDownloads,
|
pluginDownloads,
|
||||||
info,
|
info,
|
||||||
|
pluginLists,
|
||||||
].filter(notNull);
|
].filter(notNull);
|
||||||
const globalCleanup = dispatchers
|
const globalCleanup = dispatchers
|
||||||
.map((dispatcher) => dispatcher(store, logger))
|
.map((dispatcher) => dispatcher(store, logger))
|
||||||
|
|||||||
120
desktop/app/src/dispatcher/pluginLists.tsx
Normal file
120
desktop/app/src/dispatcher/pluginLists.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -78,6 +78,9 @@ type StateV2 = {
|
|||||||
}>;
|
}>;
|
||||||
deepLinkPayload: unknown;
|
deepLinkPayload: unknown;
|
||||||
staticView: StaticView;
|
staticView: StaticView;
|
||||||
|
activeClient: Client | null;
|
||||||
|
activeDevice: BaseDevice | null;
|
||||||
|
metroDevice: MetroDevice | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateV1 = Omit<StateV2, 'enabledPlugins' | 'enabledDevicePlugins'> & {
|
type StateV1 = Omit<StateV2, 'enabledPlugins' | 'enabledDevicePlugins'> & {
|
||||||
@@ -201,6 +204,9 @@ const INITAL_STATE: State = {
|
|||||||
uninitializedClients: [],
|
uninitializedClients: [],
|
||||||
deepLinkPayload: null,
|
deepLinkPayload: null,
|
||||||
staticView: WelcomeScreenStaticView,
|
staticView: WelcomeScreenStaticView,
|
||||||
|
activeClient: null,
|
||||||
|
activeDevice: null,
|
||||||
|
metroDevice: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state: State = INITAL_STATE, action: Actions): State => {
|
export default (state: State = INITAL_STATE, action: Actions): State => {
|
||||||
@@ -619,16 +625,33 @@ function updateSelection(state: Readonly<State>): State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select client based on device
|
// Select client based on device
|
||||||
const client = getBestAvailableClient(
|
updates.activeClient = getBestAvailableClient(
|
||||||
device,
|
device,
|
||||||
state.clients,
|
state.clients,
|
||||||
state.selectedApp || state.userPreferredApp,
|
state.selectedApp || state.userPreferredApp,
|
||||||
);
|
);
|
||||||
updates.selectedApp = client ? client.id : null;
|
updates.selectedApp = updates.activeClient ? updates.activeClient.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,
|
||||||
|
);
|
||||||
|
|
||||||
const availablePlugins: string[] = [
|
const availablePlugins: string[] = [
|
||||||
...(device?.devicePlugins || []),
|
...(device?.devicePlugins || []),
|
||||||
...(client?.plugins || []),
|
...(updates.activeClient?.plugins || []),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -649,6 +672,31 @@ 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,6 +64,10 @@ 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';
|
||||||
@@ -93,6 +97,7 @@ export type Actions =
|
|||||||
| HealthcheckAction
|
| HealthcheckAction
|
||||||
| TrackingAction
|
| TrackingAction
|
||||||
| PluginDownloadsAction
|
| PluginDownloadsAction
|
||||||
|
| PluginListsAction
|
||||||
| {type: 'INIT'};
|
| {type: 'INIT'};
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
@@ -110,6 +115,7 @@ 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>;
|
||||||
@@ -211,4 +217,5 @@ export default combineReducers<State, Actions>({
|
|||||||
),
|
),
|
||||||
usageTracking,
|
usageTracking,
|
||||||
pluginDownloads,
|
pluginDownloads,
|
||||||
|
pluginLists,
|
||||||
});
|
});
|
||||||
|
|||||||
68
desktop/app/src/reducers/pluginLists.tsx
Normal file
68
desktop/app/src/reducers/pluginLists.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
});
|
||||||
@@ -11,7 +11,7 @@ import React from 'react';
|
|||||||
import {Typography} from 'antd';
|
import {Typography} from 'antd';
|
||||||
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
|
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
|
||||||
import {Layout, Link, styled} from '../../ui';
|
import {Layout, Link, styled} from '../../ui';
|
||||||
import {theme, useValue, useMemoize} from 'flipper-plugin';
|
import {theme, useValue} from 'flipper-plugin';
|
||||||
import {AppSelector} from './AppSelector';
|
import {AppSelector} from './AppSelector';
|
||||||
import {useStore} from '../../utils/useStore';
|
import {useStore} from '../../utils/useStore';
|
||||||
import {PluginList} from './PluginList';
|
import {PluginList} from './PluginList';
|
||||||
@@ -19,9 +19,7 @@ import ScreenCaptureButtons from '../../chrome/ScreenCaptureButtons';
|
|||||||
import MetroButton from '../../chrome/MetroButton';
|
import MetroButton from '../../chrome/MetroButton';
|
||||||
import {BookmarkSection} from './BookmarkSection';
|
import {BookmarkSection} from './BookmarkSection';
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import {State} from '../../reducers';
|
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
import MetroDevice from '../../devices/MetroDevice';
|
|
||||||
import {ExclamationCircleOutlined, FieldTimeOutlined} from '@ant-design/icons';
|
import {ExclamationCircleOutlined, FieldTimeOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
@@ -40,20 +38,9 @@ const appTooltip = (
|
|||||||
export function AppInspect() {
|
export function AppInspect() {
|
||||||
const connections = useStore((state) => state.connections);
|
const connections = useStore((state) => state.connections);
|
||||||
|
|
||||||
const metroDevice = useMemoize(findMetroDevice, [connections.devices]);
|
const metroDevice = connections.metroDevice;
|
||||||
const client = useMemoize(findBestClient, [
|
const client = connections.activeClient;
|
||||||
connections.clients,
|
const activeDevice = connections.activeDevice;
|
||||||
connections.selectedApp,
|
|
||||||
connections.userPreferredApp,
|
|
||||||
]);
|
|
||||||
// // if the selected device is Metro, we want to keep the owner of the selected App as active device if possible
|
|
||||||
const activeDevice = useMemoize(findBestDevice, [
|
|
||||||
client,
|
|
||||||
connections.devices,
|
|
||||||
connections.selectedDevice,
|
|
||||||
metroDevice,
|
|
||||||
connections.userPreferredDevice,
|
|
||||||
]);
|
|
||||||
const isDeviceConnected = useValue(activeDevice?.connected, false);
|
const isDeviceConnected = useValue(activeDevice?.connected, false);
|
||||||
const isAppConnected = useValue(client?.connected, false);
|
const isAppConnected = useValue(client?.connected, false);
|
||||||
|
|
||||||
@@ -101,51 +88,10 @@ const Toolbar = styled(Layout.Horizontal)({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function findBestClient(
|
|
||||||
clients: Client[],
|
|
||||||
selectedApp: string | null,
|
|
||||||
userPreferredApp: string | null,
|
|
||||||
): Client | undefined {
|
|
||||||
return clients.find((c) => c.id === (selectedApp || userPreferredApp));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findMetroDevice(
|
|
||||||
devices: State['connections']['devices'],
|
|
||||||
): MetroDevice | undefined {
|
|
||||||
return devices?.find(
|
|
||||||
(device) => device.os === 'Metro' && !device.isArchived,
|
|
||||||
) as MetroDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findBestDevice(
|
|
||||||
client: Client | undefined,
|
|
||||||
devices: State['connections']['devices'],
|
|
||||||
selectedDevice: BaseDevice | null,
|
|
||||||
metroDevice: BaseDevice | undefined,
|
|
||||||
userPreferredDevice: string | null,
|
|
||||||
): BaseDevice | undefined {
|
|
||||||
// if not Metro device, use the selected device as metro device
|
|
||||||
const selected = selectedDevice ?? undefined;
|
|
||||||
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 (userPreferredDevice) {
|
|
||||||
return (
|
|
||||||
devices.find((device) => device.title === userPreferredDevice) ?? selected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStatusMessage(
|
function renderStatusMessage(
|
||||||
isDeviceConnected: boolean,
|
isDeviceConnected: boolean,
|
||||||
activeDevice: BaseDevice | undefined,
|
activeDevice: BaseDevice | null,
|
||||||
client: Client | undefined,
|
client: Client | null,
|
||||||
isAppConnected: boolean,
|
isAppConnected: boolean,
|
||||||
): React.ReactNode {
|
): React.ReactNode {
|
||||||
if (!activeDevice) {
|
if (!activeDevice) {
|
||||||
|
|||||||
@@ -20,11 +20,7 @@ import {
|
|||||||
import {Glyph, Layout, styled} from '../../ui';
|
import {Glyph, Layout, styled} from '../../ui';
|
||||||
import {theme, NUX, Tracked, useValue, useMemoize} from 'flipper-plugin';
|
import {theme, NUX, Tracked, useValue, useMemoize} from 'flipper-plugin';
|
||||||
import {useDispatch, useStore} from '../../utils/useStore';
|
import {useDispatch, useStore} from '../../utils/useStore';
|
||||||
import {
|
import {getPluginTitle, getPluginTooltip} from '../../utils/pluginUtils';
|
||||||
computePluginLists,
|
|
||||||
getPluginTitle,
|
|
||||||
getPluginTooltip,
|
|
||||||
} from '../../utils/pluginUtils';
|
|
||||||
import {selectPlugin} from '../../reducers/connections';
|
import {selectPlugin} from '../../reducers/connections';
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
@@ -52,27 +48,17 @@ export const PluginList = memo(function PluginList({
|
|||||||
activeDevice,
|
activeDevice,
|
||||||
metroDevice,
|
metroDevice,
|
||||||
}: {
|
}: {
|
||||||
client: Client | undefined;
|
client: Client | null;
|
||||||
activeDevice: BaseDevice | undefined;
|
activeDevice: BaseDevice | null;
|
||||||
metroDevice: MetroDevice | undefined;
|
metroDevice: MetroDevice | null;
|
||||||
}) {
|
}) {
|
||||||
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 downloads = useStore((state) => state.pluginDownloads);
|
const downloads = useStore((state) => state.pluginDownloads);
|
||||||
|
const isConnected = useValue(activeDevice?.connected, false);
|
||||||
// client is a mutable structure, so we need the event emitter to detect the addition of plugins....
|
const metroConnected = useValue(metroDevice?.connected, false);
|
||||||
const [pluginsChanged, setPluginsChanged] = useState(0);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const listener = () => setPluginsChanged((v) => v + 1);
|
|
||||||
client.on('plugins-change', listener);
|
|
||||||
return () => {
|
|
||||||
client.off('plugins-change', listener);
|
|
||||||
};
|
|
||||||
}, [client]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
devicePlugins,
|
devicePlugins,
|
||||||
@@ -81,17 +67,8 @@ export const PluginList = memo(function PluginList({
|
|||||||
disabledPlugins,
|
disabledPlugins,
|
||||||
unavailablePlugins,
|
unavailablePlugins,
|
||||||
downloadablePlugins,
|
downloadablePlugins,
|
||||||
} = useMemoize(computePluginLists, [
|
} = pluginLists;
|
||||||
activeDevice,
|
|
||||||
metroDevice,
|
|
||||||
client,
|
|
||||||
plugins,
|
|
||||||
connections.enabledPlugins,
|
|
||||||
connections.enabledDevicePlugins,
|
|
||||||
pluginsChanged,
|
|
||||||
]);
|
|
||||||
const isConnected = useValue(activeDevice?.connected, false);
|
|
||||||
const metroConnected = useValue(metroDevice?.connected, false);
|
|
||||||
const isArchived = activeDevice?.isArchived;
|
const isArchived = activeDevice?.isArchived;
|
||||||
|
|
||||||
const annotatedDownloadablePlugins = useMemoize<
|
const annotatedDownloadablePlugins = useMemoize<
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
createMockFlipperWithPlugin,
|
createMockFlipperWithPlugin,
|
||||||
MockFlipperResult,
|
MockFlipperResult,
|
||||||
} from '../../../test-utils/createMockFlipperWithPlugin';
|
} from '../../../test-utils/createMockFlipperWithPlugin';
|
||||||
import {findBestClient, findBestDevice, findMetroDevice} from '../AppInspect';
|
|
||||||
import {FlipperPlugin} from '../../../plugin';
|
import {FlipperPlugin} from '../../../plugin';
|
||||||
import MetroDevice from '../../../devices/MetroDevice';
|
import MetroDevice from '../../../devices/MetroDevice';
|
||||||
import BaseDevice from '../../../devices/BaseDevice';
|
import BaseDevice from '../../../devices/BaseDevice';
|
||||||
@@ -47,39 +46,21 @@ describe('basic findBestDevice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('findBestDevice prefers selected device', () => {
|
test('findBestDevice prefers selected device', () => {
|
||||||
const {client, device} = flipper;
|
const {device} = flipper;
|
||||||
const {connections} = flipper.store.getState();
|
const {connections} = flipper.store.getState();
|
||||||
expect(
|
expect(connections.activeDevice).toBe(device);
|
||||||
findBestDevice(
|
|
||||||
client,
|
|
||||||
connections.devices,
|
|
||||||
device,
|
|
||||||
undefined,
|
|
||||||
device.title,
|
|
||||||
),
|
|
||||||
).toBe(device);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findBestDevice picks device of current client', () => {
|
test('findBestDevice picks device of current client', () => {
|
||||||
const {client, device} = flipper;
|
const {device} = flipper;
|
||||||
const {connections} = flipper.store.getState();
|
const {connections} = flipper.store.getState();
|
||||||
expect(
|
expect(connections.activeDevice).toBe(device);
|
||||||
findBestDevice(client, connections.devices, null, undefined, null),
|
|
||||||
).toBe(device);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findBestDevice picks preferred device if no client and device', () => {
|
test('findBestDevice picks preferred device if no client and device', () => {
|
||||||
const {device} = flipper;
|
const {device} = flipper;
|
||||||
const {connections} = flipper.store.getState();
|
const {connections} = flipper.store.getState();
|
||||||
expect(
|
expect(connections.activeDevice).toBe(device);
|
||||||
findBestDevice(
|
|
||||||
undefined,
|
|
||||||
connections.devices,
|
|
||||||
null,
|
|
||||||
undefined,
|
|
||||||
device.title,
|
|
||||||
),
|
|
||||||
).toBe(device);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,9 +77,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 = findMetroDevice(
|
metro = flipper.store.getState().connections.metroDevice!;
|
||||||
flipper.store.getState().connections.devices,
|
|
||||||
)! as MetroDevice;
|
|
||||||
metro.supportsPlugin = (p) => {
|
metro.supportsPlugin = (p) => {
|
||||||
return p.id !== 'unsupportedDevicePlugin';
|
return p.id !== 'unsupportedDevicePlugin';
|
||||||
};
|
};
|
||||||
@@ -118,13 +97,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
userPreferredPlugin: 'DeviceLogs',
|
userPreferredPlugin: 'DeviceLogs',
|
||||||
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
|
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
|
||||||
});
|
});
|
||||||
expect(
|
expect(connections.activeClient).toBe(flipper.client);
|
||||||
findBestClient(
|
|
||||||
connections.clients,
|
|
||||||
connections.selectedApp,
|
|
||||||
connections.userPreferredApp,
|
|
||||||
),
|
|
||||||
).toBe(flipper.client);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('selecting Metro Logs works but keeps normal device preferred', () => {
|
test('selecting Metro Logs works but keeps normal device preferred', () => {
|
||||||
@@ -147,37 +120,14 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
});
|
});
|
||||||
const {connections} = flipper.store.getState();
|
const {connections} = flipper.store.getState();
|
||||||
// find best device is still metro
|
// find best device is still metro
|
||||||
expect(
|
expect(connections.activeDevice).toBe(testDevice);
|
||||||
findBestDevice(
|
|
||||||
undefined,
|
|
||||||
connections.devices,
|
|
||||||
connections.selectedDevice,
|
|
||||||
metro,
|
|
||||||
connections.userPreferredDevice,
|
|
||||||
),
|
|
||||||
).toBe(testDevice);
|
|
||||||
// find best client still returns app
|
// find best client still returns app
|
||||||
expect(
|
expect(connections.activeClient).toBe(flipper.client);
|
||||||
findBestClient(
|
|
||||||
connections.clients,
|
|
||||||
connections.selectedApp,
|
|
||||||
connections.userPreferredApp,
|
|
||||||
),
|
|
||||||
).toBe(flipper.client);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('computePluginLists', () => {
|
test('computePluginLists', () => {
|
||||||
const state = flipper.store.getState();
|
const state = flipper.store.getState();
|
||||||
expect(
|
expect(computePluginLists(state.connections, state.plugins)).toEqual({
|
||||||
computePluginLists(
|
|
||||||
testDevice,
|
|
||||||
metro,
|
|
||||||
flipper.client,
|
|
||||||
state.plugins,
|
|
||||||
state.connections.enabledPlugins,
|
|
||||||
state.connections.enabledDevicePlugins,
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
downloadablePlugins: [],
|
downloadablePlugins: [],
|
||||||
devicePlugins: [logsPlugin],
|
devicePlugins: [logsPlugin],
|
||||||
metroPlugins: [logsPlugin],
|
metroPlugins: [logsPlugin],
|
||||||
@@ -281,14 +231,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let state = flipper.store.getState();
|
let state = flipper.store.getState();
|
||||||
const pluginLists = computePluginLists(
|
const pluginLists = computePluginLists(state.connections, state.plugins);
|
||||||
testDevice,
|
|
||||||
metro,
|
|
||||||
flipper.client,
|
|
||||||
state.plugins,
|
|
||||||
state.connections.enabledPlugins,
|
|
||||||
state.connections.enabledDevicePlugins,
|
|
||||||
);
|
|
||||||
expect(pluginLists).toEqual({
|
expect(pluginLists).toEqual({
|
||||||
devicePlugins: [logsPlugin],
|
devicePlugins: [logsPlugin],
|
||||||
metroPlugins: [logsPlugin],
|
metroPlugins: [logsPlugin],
|
||||||
@@ -322,16 +265,7 @@ describe('basic findBestDevice with metro present', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
state = flipper.store.getState();
|
state = flipper.store.getState();
|
||||||
expect(
|
expect(computePluginLists(state.connections, state.plugins)).toMatchObject({
|
||||||
computePluginLists(
|
|
||||||
testDevice,
|
|
||||||
metro,
|
|
||||||
flipper.client,
|
|
||||||
state.plugins,
|
|
||||||
state.connections.enabledPlugins,
|
|
||||||
state.connections.enabledDevicePlugins,
|
|
||||||
),
|
|
||||||
).toMatchObject({
|
|
||||||
enabledPlugins: [plugin2],
|
enabledPlugins: [plugin2],
|
||||||
disabledPlugins: [plugin1],
|
disabledPlugins: [plugin1],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,15 +83,7 @@ export function getExportablePlugins(
|
|||||||
device: BaseDevice | undefined | null,
|
device: BaseDevice | undefined | null,
|
||||||
client?: Client,
|
client?: Client,
|
||||||
): {id: string; label: string}[] {
|
): {id: string; label: string}[] {
|
||||||
const availablePlugins = computePluginLists(
|
const availablePlugins = computePluginLists(state.connections, state.plugins);
|
||||||
device ?? undefined,
|
|
||||||
undefined,
|
|
||||||
client,
|
|
||||||
state.plugins,
|
|
||||||
state.connections.enabledPlugins,
|
|
||||||
state.connections.enabledDevicePlugins,
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...availablePlugins.devicePlugins.filter((plugin) => {
|
...availablePlugins.devicePlugins.filter((plugin) => {
|
||||||
return isExportablePlugin(state, device, client, plugin);
|
return isExportablePlugin(state, device, client, plugin);
|
||||||
@@ -180,14 +172,38 @@ export function getPluginTooltip(details: PluginDetails): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function computePluginLists(
|
export function computePluginLists(
|
||||||
device: BaseDevice | undefined,
|
connections: Pick<
|
||||||
metroDevice: BaseDevice | undefined,
|
State['connections'],
|
||||||
client: Client | undefined,
|
| 'activeDevice'
|
||||||
plugins: State['plugins'],
|
| 'activeClient'
|
||||||
enabledPluginsState: State['connections']['enabledPlugins'],
|
| 'metroDevice'
|
||||||
enabledDevicePluginsState: Set<string>,
|
| 'enabledDevicePlugins'
|
||||||
_pluginsChanged?: number, // this argument is purely used to invalidate the memoization cache
|
| 'enabledPlugins'
|
||||||
) {
|
>,
|
||||||
|
plugins: Pick<
|
||||||
|
State['plugins'],
|
||||||
|
| 'bundledPlugins'
|
||||||
|
| 'marketplacePlugins'
|
||||||
|
| 'loadedPlugins'
|
||||||
|
| 'devicePlugins'
|
||||||
|
| 'disabledPlugins'
|
||||||
|
| 'gatekeepedPlugins'
|
||||||
|
| 'failedPlugins'
|
||||||
|
| 'clientPlugins'
|
||||||
|
>,
|
||||||
|
): {
|
||||||
|
devicePlugins: DevicePluginDefinition[];
|
||||||
|
metroPlugins: DevicePluginDefinition[];
|
||||||
|
enabledPlugins: ClientPluginDefinition[];
|
||||||
|
disabledPlugins: PluginDefinition[];
|
||||||
|
unavailablePlugins: [plugin: PluginDetails, reason: string][];
|
||||||
|
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
|
||||||
|
} {
|
||||||
|
const device = connections.activeDevice;
|
||||||
|
const client = connections.activeClient;
|
||||||
|
const metroDevice = connections.metroDevice;
|
||||||
|
const enabledDevicePluginsState = connections.enabledDevicePlugins;
|
||||||
|
const enabledPluginsState = connections.enabledPlugins;
|
||||||
const uninstalledMarketplacePlugins = getLatestCompatibleVersionOfEachPlugin([
|
const uninstalledMarketplacePlugins = getLatestCompatibleVersionOfEachPlugin([
|
||||||
...plugins.bundledPlugins.values(),
|
...plugins.bundledPlugins.values(),
|
||||||
...plugins.marketplacePlugins,
|
...plugins.marketplacePlugins,
|
||||||
|
|||||||
Reference in New Issue
Block a user