Rework sidebar selection
Summary: - Make sure newly connecting apps are automatically selected - Improved the sidebar UI by using more consistent, spacious styling, and giving some more attention to error states Reviewed By: passy Differential Revision: D18505636 fbshipit-source-id: 18b2c8e78be13aabb3a54c60553f6b0d1e613b27
This commit is contained in:
committed by
Facebook Github Bot
parent
5bd0cd3d63
commit
9f7be13e39
@@ -40,16 +40,13 @@ export type State = {
|
||||
androidEmulators: Array<string>;
|
||||
selectedDevice: null | BaseDevice;
|
||||
selectedPlugin: null | string;
|
||||
selectedApp: null | string;
|
||||
selectedApp: null | string | undefined;
|
||||
userPreferredDevice: null | string;
|
||||
userPreferredPlugin: null | string;
|
||||
userPreferredApp: null | string;
|
||||
userStarredPlugins: {[key: string]: Array<string>};
|
||||
errors: FlipperError[];
|
||||
clients: Array<Client>;
|
||||
// refers to the client that is selected in the main side bar, not to be confused with
|
||||
// selectedApp, which represents the app of the currently active plugin!
|
||||
selectedClient: string;
|
||||
uninitializedClients: Array<{
|
||||
client: UninitializedClient;
|
||||
deviceId?: string;
|
||||
@@ -154,7 +151,6 @@ const INITAL_STATE: State = {
|
||||
userStarredPlugins: {},
|
||||
errors: [],
|
||||
clients: [],
|
||||
selectedClient: '',
|
||||
uninitializedClients: [],
|
||||
deepLinkPayload: null,
|
||||
staticView: WelcomeScreen,
|
||||
@@ -173,14 +169,13 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
}
|
||||
case 'SELECT_DEVICE': {
|
||||
const {payload} = action;
|
||||
return {
|
||||
return updateSelection({
|
||||
...state,
|
||||
selectedApp: null,
|
||||
staticView: null,
|
||||
selectedPlugin: DEFAULT_PLUGIN,
|
||||
selectedDevice: payload,
|
||||
userPreferredDevice: payload.title,
|
||||
};
|
||||
userPreferredDevice: payload
|
||||
? payload.title
|
||||
: state.userPreferredDevice,
|
||||
});
|
||||
}
|
||||
case 'REGISTER_ANDROID_EMULATORS': {
|
||||
const {payload} = action;
|
||||
@@ -191,76 +186,27 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
}
|
||||
case 'REGISTER_DEVICE': {
|
||||
const {payload} = action;
|
||||
const devices = state.devices.concat(payload);
|
||||
let {selectedDevice, selectedPlugin} = state;
|
||||
let staticView: StaticView = state.staticView;
|
||||
// select the default plugin
|
||||
let selection: Partial<State> = {
|
||||
selectedApp: null,
|
||||
selectedPlugin: DEFAULT_PLUGIN,
|
||||
};
|
||||
|
||||
const canBeDefaultDevice = !DEFAULT_DEVICE_BLACKLIST.some(
|
||||
blacklistedDevice => payload instanceof blacklistedDevice,
|
||||
);
|
||||
|
||||
if (!selectedDevice && canBeDefaultDevice) {
|
||||
selectedDevice = payload;
|
||||
staticView = null;
|
||||
if (selectedPlugin) {
|
||||
// We already had a plugin selected, but no device. This is happening
|
||||
// when the Client connected before the Device.
|
||||
selection = {};
|
||||
}
|
||||
} else if (payload.title === state.userPreferredDevice) {
|
||||
selectedDevice = payload;
|
||||
staticView = null;
|
||||
} else {
|
||||
// We didn't select the newly connected device, so we don't want to
|
||||
// change the plugin.
|
||||
selection = {};
|
||||
}
|
||||
|
||||
return {
|
||||
return updateSelection({
|
||||
...state,
|
||||
devices,
|
||||
// select device if none was selected before
|
||||
selectedDevice,
|
||||
...selection,
|
||||
staticView,
|
||||
};
|
||||
devices: state.devices.concat(payload),
|
||||
});
|
||||
}
|
||||
|
||||
case 'UNREGISTER_DEVICES': {
|
||||
const {payload} = action;
|
||||
const {selectedDevice} = state;
|
||||
let selectedDeviceWasRemoved = false;
|
||||
const devices = state.devices.filter((device: BaseDevice) => {
|
||||
if (payload.has(device.serial)) {
|
||||
if (selectedDevice === device) {
|
||||
// removed device is the selected
|
||||
selectedDeviceWasRemoved = true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let selection = {};
|
||||
if (selectedDeviceWasRemoved) {
|
||||
selection = {
|
||||
selectedDevice: devices[devices.length - 1] || null,
|
||||
staticView: selectedDevice != null ? null : WelcomeScreen,
|
||||
selectedApp: null,
|
||||
selectedPlugin: DEFAULT_PLUGIN,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
return updateSelection({
|
||||
...state,
|
||||
devices,
|
||||
...selection,
|
||||
};
|
||||
});
|
||||
}
|
||||
case 'SELECT_PLUGIN': {
|
||||
const {payload} = action;
|
||||
@@ -269,15 +215,14 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
performance.mark(`activePlugin-${selectedPlugin}`);
|
||||
}
|
||||
|
||||
const userPreferredApp = selectedApp || state.userPreferredApp;
|
||||
return {
|
||||
return updateSelection({
|
||||
...state,
|
||||
...payload,
|
||||
staticView: null,
|
||||
userPreferredApp: userPreferredApp,
|
||||
userPreferredPlugin: selectedPlugin,
|
||||
};
|
||||
selectedApp,
|
||||
selectedPlugin,
|
||||
userPreferredPlugin: selectedPlugin || state.userPreferredPlugin,
|
||||
});
|
||||
}
|
||||
|
||||
case 'STAR_PLUGIN': {
|
||||
const {selectedPlugin, selectedApp} = action.payload;
|
||||
const starredPluginsForApp = [
|
||||
@@ -302,22 +247,11 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
const {payload} = action;
|
||||
return {...state, userPreferredPlugin: payload};
|
||||
}
|
||||
|
||||
case 'NEW_CLIENT': {
|
||||
const {payload} = action;
|
||||
const {userPreferredApp, userPreferredPlugin} = state;
|
||||
let {selectedApp, selectedPlugin} = state;
|
||||
|
||||
if (
|
||||
userPreferredApp &&
|
||||
userPreferredPlugin &&
|
||||
payload.id === userPreferredApp &&
|
||||
payload.plugins.includes(userPreferredPlugin)
|
||||
) {
|
||||
// user preferred client did reconnect, so let's select it
|
||||
selectedApp = userPreferredApp;
|
||||
selectedPlugin = userPreferredPlugin;
|
||||
}
|
||||
return {
|
||||
return updateSelection({
|
||||
...state,
|
||||
clients: state.clients.concat(payload),
|
||||
uninitializedClients: state.uninitializedClients.filter(c => {
|
||||
@@ -326,10 +260,18 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
c.client.appName !== payload.query.app
|
||||
);
|
||||
}),
|
||||
selectedApp,
|
||||
selectedPlugin,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
case 'SELECT_CLIENT': {
|
||||
const {payload} = action;
|
||||
return updateSelection({
|
||||
...state,
|
||||
selectedApp: payload,
|
||||
userPreferredApp: payload || state.userPreferredApp,
|
||||
});
|
||||
}
|
||||
|
||||
case 'NEW_CLIENT_SANITY_CHECK': {
|
||||
const {payload} = action;
|
||||
// Check for clients initialised when there is no matching device
|
||||
@@ -346,26 +288,19 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
case 'CLIENT_REMOVED': {
|
||||
const {payload} = action;
|
||||
|
||||
const selected: Partial<State> = {};
|
||||
if (state.selectedApp === payload) {
|
||||
selected.selectedApp = null;
|
||||
selected.selectedPlugin = DEFAULT_PLUGIN;
|
||||
}
|
||||
|
||||
return {
|
||||
return updateSelection({
|
||||
...state,
|
||||
...selected,
|
||||
clients: state.clients.filter(
|
||||
(client: Client) => client.id !== payload,
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
case 'PREFER_DEVICE': {
|
||||
const {payload: userPreferredDevice} = action;
|
||||
return {...state, userPreferredDevice};
|
||||
@@ -433,12 +368,6 @@ const reducer = (state: State = INITAL_STATE, action: Actions): State => {
|
||||
errors,
|
||||
};
|
||||
}
|
||||
case 'SELECT_CLIENT': {
|
||||
return {
|
||||
...state,
|
||||
selectedClient: action.payload,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -523,11 +452,6 @@ export const starPlugin = (payload: {
|
||||
payload,
|
||||
});
|
||||
|
||||
export const userPreferredPlugin = (payload: string): Action => ({
|
||||
type: 'SELECT_USER_PREFERRED_PLUGIN',
|
||||
payload,
|
||||
});
|
||||
|
||||
export const dismissError = (index: number): Action => ({
|
||||
type: 'DISMISS_ERROR',
|
||||
payload: index,
|
||||
@@ -537,3 +461,112 @@ export const selectClient = (clientId: string): Action => ({
|
||||
type: 'SELECT_CLIENT',
|
||||
payload: clientId,
|
||||
});
|
||||
|
||||
export function getAvailableClients(
|
||||
device: null | undefined | BaseDevice,
|
||||
clients: Client[],
|
||||
): Client[] {
|
||||
if (!device) {
|
||||
return [];
|
||||
}
|
||||
return clients
|
||||
.filter(
|
||||
(client: Client) =>
|
||||
(device &&
|
||||
device.supportsOS(client.query.os) &&
|
||||
client.query.device_id === device.serial) ||
|
||||
// Old android sdk versions don't know their device_id
|
||||
// Display their plugins under all selected devices until they die out
|
||||
client.query.device_id === 'unknown',
|
||||
)
|
||||
.sort((a, b) => (a.query.app || '').localeCompare(b.query.app));
|
||||
}
|
||||
|
||||
function getBestAvailableClient(
|
||||
device: BaseDevice | null | undefined,
|
||||
clients: Client[],
|
||||
preferredClient: string | null,
|
||||
): Client | undefined {
|
||||
const availableClients = getAvailableClients(device, clients);
|
||||
if (availableClients.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
getClientById(availableClients, preferredClient) ||
|
||||
availableClients[0] ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
export function getClientById(
|
||||
clients: Client[],
|
||||
clientId: string | null | undefined,
|
||||
): Client | undefined {
|
||||
return clients.find(client => client.id === clientId);
|
||||
}
|
||||
|
||||
function canBeDefaultDevice(device: BaseDevice) {
|
||||
return !DEFAULT_DEVICE_BLACKLIST.some(
|
||||
blacklistedDevice => device instanceof blacklistedDevice,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function, given the current state, tries to build to build the best
|
||||
* selection possible, preselection device if there is non, plugins based on preferences, etc
|
||||
* @param state
|
||||
*/
|
||||
function updateSelection(state: Readonly<State>): State {
|
||||
const updates: Partial<State> = {
|
||||
staticView: null,
|
||||
};
|
||||
// Find the selected device if it still exists
|
||||
let device: BaseDevice | null =
|
||||
state.selectedDevice && state.devices.includes(state.selectedDevice)
|
||||
? state.selectedDevice
|
||||
: null;
|
||||
if (!device) {
|
||||
device =
|
||||
state.devices.find(
|
||||
device => device.title === state.userPreferredDevice,
|
||||
) ||
|
||||
state.devices.find(device => canBeDefaultDevice(device)) ||
|
||||
null;
|
||||
}
|
||||
updates.selectedDevice = device;
|
||||
if (!device) {
|
||||
updates.staticView = WelcomeScreen;
|
||||
}
|
||||
|
||||
// Select client based on device
|
||||
const client = getBestAvailableClient(
|
||||
device,
|
||||
state.clients,
|
||||
state.selectedApp || state.userPreferredApp,
|
||||
);
|
||||
updates.selectedApp = client ? client.id : null;
|
||||
|
||||
const availablePlugins: string[] = [
|
||||
...(device?.devicePlugins || []),
|
||||
...(client?.plugins || []),
|
||||
];
|
||||
|
||||
if (
|
||||
// Try the preferred plugin first
|
||||
state.userPreferredPlugin &&
|
||||
availablePlugins.includes(state.userPreferredPlugin)
|
||||
) {
|
||||
updates.selectedPlugin = state.userPreferredPlugin;
|
||||
} else if (
|
||||
!state.selectedPlugin ||
|
||||
!availablePlugins.includes(state.selectedPlugin)
|
||||
) {
|
||||
// currently selected plugin is not available in this state,
|
||||
// fall back to the default
|
||||
updates.selectedPlugin = DEFAULT_PLUGIN;
|
||||
}
|
||||
|
||||
const res = {...state, ...updates};
|
||||
console.log(res.selectedDevice, res.selectedApp, res.selectedPlugin);
|
||||
return res;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user