From 05328167c6a9441ca4f7330d7ae15b4f353ff78b Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Wed, 2 Oct 2019 05:46:25 -0700 Subject: [PATCH] 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 --- src/App.tsx | 2 + src/chrome/StatusBar.tsx | 50 ++++++++++++++++ src/chrome/__tests__/StatusBar.node.tsx | 18 ++++++ .../__tests__/applications.electron.tsx | 54 ++++++++++++++++++ src/reducers/application.tsx | 57 ++++++++++++++++++- 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/chrome/StatusBar.tsx create mode 100644 src/chrome/__tests__/StatusBar.node.tsx create mode 100644 src/reducers/__tests__/applications.electron.tsx diff --git a/src/App.tsx b/src/App.tsx index cc7637d5a..c086e7e28 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 { )} + ); diff --git a/src/chrome/StatusBar.tsx b/src/chrome/StatusBar.tsx new file mode 100644 index 000000000..34c70a6d2 --- /dev/null +++ b/src/chrome/StatusBar.tsx @@ -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 ( + + {statusMessage} + + ); + } else { + return null; + } +} + +export default connect( + ({application: {statusMessages}}) => { + if (statusMessages.length > 0) { + return {statusMessage: statusMessages[statusMessages.length - 1]}; + } + return { + statusMessage: null, + }; + }, +)(statusBarView); diff --git a/src/chrome/__tests__/StatusBar.node.tsx b/src/chrome/__tests__/StatusBar.node.tsx new file mode 100644 index 000000000..f97231576 --- /dev/null +++ b/src/chrome/__tests__/StatusBar.node.tsx @@ -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(); +}); diff --git a/src/reducers/__tests__/applications.electron.tsx b/src/reducers/__tests__/applications.electron.tsx new file mode 100644 index 000000000..151bccc71 --- /dev/null +++ b/src/reducers/__tests__/applications.electron.tsx @@ -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([]); +}); diff --git a/src/reducers/application.tsx b/src/reducers/application.tsx index df7b21483..28975188e 100644 --- a/src/reducers/application.tsx +++ b/src/reducers/application.tsx @@ -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; }; 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, +});