Add support for status bar

Summary:
This diff adds a status bar, which can be used to show the status messages, for example for Litho Support Form.
The logic for the status bar is as follows:

It maintains the array of the messages. At any point it shows the last pushed message. It will keep showing that message until it is being removed, once removed it will show second last message. The messages will be removed as and when its corresponding task/Promise is fulfilled.

Reviewed By: danielbuechele

Differential Revision: D17551495

fbshipit-source-id: 96b2f401599b9ee8a472607e6a2f027e63b3b807
This commit is contained in:
Pritesh Nandgaonkar
2019-10-02 05:46:25 -07:00
committed by Facebook Github Bot
parent 4ec8ffcf53
commit 05328167c6
5 changed files with 180 additions and 1 deletions

View File

@@ -36,6 +36,7 @@ import {State as Store} from './reducers/index';
import {StaticView} from './reducers/connections';
import PluginManager from './chrome/PluginManager';
import BaseDevice from './devices/BaseDevice';
import StatusBar from './chrome/StatusBar';
const version = remote.app.getVersion();
type OwnProps = {
@@ -122,6 +123,7 @@ export class App extends React.Component<Props> {
<PluginContainer logger={this.props.logger} />
)}
</FlexRow>
<StatusBar />
<ErrorBar text={this.props.error} />
</FlexColumn>
);

50
src/chrome/StatusBar.tsx Normal file
View File

@@ -0,0 +1,50 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {colors} from '../ui/components/colors';
import {styled} from '../ui';
import {connect} from 'react-redux';
import {State} from '../reducers';
import React, {ReactElement} from 'react';
import Text from '../ui/components/Text';
const StatusBarContainer = styled(Text)({
backgroundColor: colors.macOSTitleBarBackgroundBlur,
borderTop: '1px solid #b3b3b3',
lineHeight: '26px',
padding: '0 10px',
textOverflow: 'ellipsis',
overflow: 'hidden',
});
type Props = {
statusMessage: string | null;
};
export function statusBarView(props: Props): ReactElement | null {
const {statusMessage} = props;
if (statusMessage) {
return (
<StatusBarContainer italic={true} whiteSpace="nowrap">
{statusMessage}
</StatusBarContainer>
);
} else {
return null;
}
}
export default connect<Props, void, void, State>(
({application: {statusMessages}}) => {
if (statusMessages.length > 0) {
return {statusMessage: statusMessages[statusMessages.length - 1]};
}
return {
statusMessage: null,
};
},
)(statusBarView);

View File

@@ -0,0 +1,18 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {statusBarView} from '../StatusBar';
test('statusBarView returns null for empty status messages', () => {
const view = statusBarView({statusMessage: null});
expect(view).toBeNull();
});
test('statusBarView returns non null view when the list of messages is non empty', () => {
const view = statusBarView({statusMessage: 'Last Message'});
expect(view).toBeDefined();
});

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import reducer from '../application';
import {
initialState,
addStatusMessage,
removeStatusMessage,
} from '../application';
test('ADD_STATUS_MSG, to check if the status messages get pushed to the state', () => {
const state = reducer(
initialState(),
addStatusMessage({msg: 'Status Msg', sender: 'Test'}),
);
expect(state.statusMessages).toEqual(['Test: Status Msg']);
const updatedstate = reducer(
state,
addStatusMessage({msg: 'Status Msg 2', sender: 'Test'}),
);
expect(updatedstate.statusMessages).toEqual([
'Test: Status Msg',
'Test: Status Msg 2',
]);
});
test('REMOVE_STATUS_MSG, to check if the status messages gets removed from the state', () => {
const initState = initialState();
const state = reducer(
initState,
removeStatusMessage({msg: 'Status Msg', sender: 'Test'}),
);
expect(state).toEqual(initState);
const stateWithMessages = reducer(
reducer(
initialState(),
addStatusMessage({msg: 'Status Msg', sender: 'Test'}),
),
addStatusMessage({msg: 'Status Msg 2', sender: 'Test'}),
);
const updatedState = reducer(
stateWithMessages,
removeStatusMessage({msg: 'Status Msg', sender: 'Test'}),
);
expect(updatedState.statusMessages).toEqual(['Test: Status Msg 2']);
const updatedStateWithNoMessages = reducer(
updatedState,
removeStatusMessage({msg: 'Status Msg 2', sender: 'Test'}),
);
expect(updatedStateWithNoMessages.statusMessages).toEqual([]);
});

View File

@@ -42,6 +42,11 @@ export type ServerPorts = {
secure: number;
};
type StatusMessageType = {
msg: string;
sender: string;
};
type SubShareType =
| {
type: 'file';
@@ -68,6 +73,7 @@ export type State = {
downloadingImportData: boolean;
launcherMsg: LauncherMsg;
flipperRating: number | null;
statusMessages: Array<string>;
};
type BooleanActionType =
@@ -124,9 +130,17 @@ export type Action =
| {
type: 'SET_EXPORT_URL';
payload: string;
}
| {
type: 'ADD_STATUS_MSG';
payload: {msg: string; sender: string};
}
| {
type: 'REMOVE_STATUS_MSG';
payload: {msg: string; sender: string};
};
const initialState: () => State = () => ({
export const initialState: () => State = () => ({
leftSidebarVisible: true,
rightSidebarVisible: true,
rightSidebarAvailable: false,
@@ -144,8 +158,20 @@ const initialState: () => State = () => ({
message: '',
},
flipperRating: null,
statusMessages: [],
});
function statusMessage(sender: string, msg: string): string {
const messageTrimmed = msg.trim();
const senderTrimmed = sender.trim();
let statusMessage = senderTrimmed.length > 0 ? senderTrimmed : '';
statusMessage =
statusMessage.length > 0 && messageTrimmed.length > 0
? `${statusMessage}: ${messageTrimmed}`
: '';
return statusMessage;
}
export default function reducer(
state: State | undefined,
action: Actions,
@@ -221,6 +247,25 @@ export default function reducer(
return {...state, share: {...share, url: action.payload}};
}
return state;
} else if (action.type === 'ADD_STATUS_MSG') {
const {sender, msg} = action.payload;
const statusMsg = statusMessage(sender, msg);
if (statusMsg.length > 0) {
return {
...state,
statusMessages: [...state.statusMessages, statusMsg],
};
}
return state;
} else if (action.type === 'REMOVE_STATUS_MSG') {
const {sender, msg} = action.payload;
const statusMsg = statusMessage(sender, msg);
if (statusMsg.length > 0) {
const statusMessages = [...state.statusMessages];
statusMessages.splice(statusMessages.indexOf(statusMsg), 1);
return {...state, statusMessages};
}
return state;
} else {
return state;
}
@@ -288,3 +333,13 @@ export const setExportURL = (result: string): Action => ({
type: 'SET_EXPORT_URL',
payload: result,
});
export const addStatusMessage = (payload: StatusMessageType): Action => ({
type: 'ADD_STATUS_MSG',
payload,
});
export const removeStatusMessage = (payload: StatusMessageType): Action => ({
type: 'REMOVE_STATUS_MSG',
payload,
});