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:
committed by
Facebook Github Bot
parent
4ec8ffcf53
commit
05328167c6
@@ -36,6 +36,7 @@ import {State as Store} from './reducers/index';
|
|||||||
import {StaticView} from './reducers/connections';
|
import {StaticView} from './reducers/connections';
|
||||||
import PluginManager from './chrome/PluginManager';
|
import PluginManager from './chrome/PluginManager';
|
||||||
import BaseDevice from './devices/BaseDevice';
|
import BaseDevice from './devices/BaseDevice';
|
||||||
|
import StatusBar from './chrome/StatusBar';
|
||||||
const version = remote.app.getVersion();
|
const version = remote.app.getVersion();
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@@ -122,6 +123,7 @@ export class App extends React.Component<Props> {
|
|||||||
<PluginContainer logger={this.props.logger} />
|
<PluginContainer logger={this.props.logger} />
|
||||||
)}
|
)}
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
|
<StatusBar />
|
||||||
<ErrorBar text={this.props.error} />
|
<ErrorBar text={this.props.error} />
|
||||||
</FlexColumn>
|
</FlexColumn>
|
||||||
);
|
);
|
||||||
|
|||||||
50
src/chrome/StatusBar.tsx
Normal file
50
src/chrome/StatusBar.tsx
Normal 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);
|
||||||
18
src/chrome/__tests__/StatusBar.node.tsx
Normal file
18
src/chrome/__tests__/StatusBar.node.tsx
Normal 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();
|
||||||
|
});
|
||||||
54
src/reducers/__tests__/applications.electron.tsx
Normal file
54
src/reducers/__tests__/applications.electron.tsx
Normal 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([]);
|
||||||
|
});
|
||||||
@@ -42,6 +42,11 @@ export type ServerPorts = {
|
|||||||
secure: number;
|
secure: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StatusMessageType = {
|
||||||
|
msg: string;
|
||||||
|
sender: string;
|
||||||
|
};
|
||||||
|
|
||||||
type SubShareType =
|
type SubShareType =
|
||||||
| {
|
| {
|
||||||
type: 'file';
|
type: 'file';
|
||||||
@@ -68,6 +73,7 @@ export type State = {
|
|||||||
downloadingImportData: boolean;
|
downloadingImportData: boolean;
|
||||||
launcherMsg: LauncherMsg;
|
launcherMsg: LauncherMsg;
|
||||||
flipperRating: number | null;
|
flipperRating: number | null;
|
||||||
|
statusMessages: Array<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BooleanActionType =
|
type BooleanActionType =
|
||||||
@@ -124,9 +130,17 @@ export type Action =
|
|||||||
| {
|
| {
|
||||||
type: 'SET_EXPORT_URL';
|
type: 'SET_EXPORT_URL';
|
||||||
payload: string;
|
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,
|
leftSidebarVisible: true,
|
||||||
rightSidebarVisible: true,
|
rightSidebarVisible: true,
|
||||||
rightSidebarAvailable: false,
|
rightSidebarAvailable: false,
|
||||||
@@ -144,8 +158,20 @@ const initialState: () => State = () => ({
|
|||||||
message: '',
|
message: '',
|
||||||
},
|
},
|
||||||
flipperRating: null,
|
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(
|
export default function reducer(
|
||||||
state: State | undefined,
|
state: State | undefined,
|
||||||
action: Actions,
|
action: Actions,
|
||||||
@@ -221,6 +247,25 @@ export default function reducer(
|
|||||||
return {...state, share: {...share, url: action.payload}};
|
return {...state, share: {...share, url: action.payload}};
|
||||||
}
|
}
|
||||||
return state;
|
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 {
|
} else {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -288,3 +333,13 @@ export const setExportURL = (result: string): Action => ({
|
|||||||
type: 'SET_EXPORT_URL',
|
type: 'SET_EXPORT_URL',
|
||||||
payload: result,
|
payload: result,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const addStatusMessage = (payload: StatusMessageType): Action => ({
|
||||||
|
type: 'ADD_STATUS_MSG',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeStatusMessage = (payload: StatusMessageType): Action => ({
|
||||||
|
type: 'REMOVE_STATUS_MSG',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user