diff --git a/src/plugins/navigation/__tests__/testNavigationPlugin.node.js b/src/plugins/navigation/__tests__/testNavigationPlugin.node.js deleted file mode 100644 index 3588c665c..000000000 --- a/src/plugins/navigation/__tests__/testNavigationPlugin.node.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * 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 - * @flow strict-local - */ - -import NavigationPlugin from '../'; -import {DefaultProvider} from '../util/autoCompleteProvider'; - -import type {Bookmark, PersistedState, URI} from '../flow-types'; - -function constructPersistedStateMock(): PersistedState { - return { - currentURI: '', - appMatchPatterns: [], - appMatchPatternsProvider: new DefaultProvider(), - bookmarksProvider: new DefaultProvider(), - bookmarks: new Map(), - navigationEvents: [], - }; -} - -function constructPersistedStateMockWithEvents(): PersistedState { - return { - currentURI: '', - appMatchPatterns: [], - appMatchPatternsProvider: new DefaultProvider(), - bookmarksProvider: new DefaultProvider(), - bookmarks: new Map(), - navigationEvents: [ - { - uri: 'mock://this_is_a_mock_uri/mock/1', - date: DATE_MOCK_2, - }, - { - uri: 'mock://this_is_a_mock_uri/mock/2', - date: DATE_MOCK_3, - }, - ], - }; -} - -const DATE_MOCK_1 = new Date(2019, 6, 17, 11, 10, 0, 0); -const DATE_MOCK_2 = new Date(2019, 6, 18, 11, 10, 0, 0); -const DATE_MOCK_3 = new Date(2019, 6, 19, 11, 10, 0, 0); - -const INCOMING_NAV_EVENT = { - uri: 'mock://this_is_a_mock_uri/mock', - date: DATE_MOCK_1, -}; - -const INCOMING_UNDEFINED_NAV_EVENT = { - date: DATE_MOCK_1, -}; - -test('add incoming nav event to persisted state', () => { - const persistedState = constructPersistedStateMock(); - const reducer = NavigationPlugin.persistedStateReducer; - if (reducer) { - const newPersistedState = reducer( - persistedState, - 'nav_event', - INCOMING_NAV_EVENT, - ); - expect(newPersistedState.navigationEvents).toEqual([ - { - uri: 'mock://this_is_a_mock_uri/mock', - date: DATE_MOCK_1, - }, - ]); - } else { - expect(reducer).not.toBeNull(); - } -}); - -test('add incoming nav event to persisted state with nav events', () => { - const persistedState = constructPersistedStateMockWithEvents(); - const reducer = NavigationPlugin.persistedStateReducer; - if (reducer) { - const newPersistedState = reducer( - persistedState, - 'nav_event', - INCOMING_NAV_EVENT, - ); - expect(newPersistedState.navigationEvents).toEqual([ - { - uri: 'mock://this_is_a_mock_uri/mock', - date: DATE_MOCK_1, - }, - ...persistedState.navigationEvents, - ]); - } else { - expect(reducer).not.toBeNull(); - } -}); - -test('add incoming nav event with undefined uri to persisted state', () => { - const persistedState = constructPersistedStateMock(); - const reducer = NavigationPlugin.persistedStateReducer; - if (reducer) { - const newPersistedState = reducer( - persistedState, - 'nav_event', - INCOMING_UNDEFINED_NAV_EVENT, - ); - expect(newPersistedState.navigationEvents).toEqual([ - { - uri: null, - date: DATE_MOCK_1, - }, - ]); - } else { - expect(reducer).not.toBeNull(); - } -}); diff --git a/src/plugins/navigation/components/Timeline.js b/src/plugins/navigation/components/Timeline.js index 4220f3714..8db6fa54d 100644 --- a/src/plugins/navigation/components/Timeline.js +++ b/src/plugins/navigation/components/Timeline.js @@ -6,7 +6,7 @@ * @flow strict-local */ -import {colors, FlexCenter, styled} from 'flipper'; +import {colors, FlexCenter, styled, LoadingIndicator} from 'flipper'; import {NavigationInfoBox} from './'; import type {Bookmark, NavigationEvent} from '../flow-types'; @@ -23,6 +23,11 @@ const TimelineContainer = styled('div')({ flexGrow: 1, }); +const NavigationEventContainer = styled('div')({ + display: 'flex', + margin: 20, +}); + const NoData = styled(FlexCenter)({ height: '100%', fontSize: 18, @@ -30,6 +35,18 @@ const NoData = styled(FlexCenter)({ color: colors.macOSTitleBarIcon, }); +const ScreenshotContainer = styled('div')({ + width: 200, + minWidth: 200, + margin: 10, + border: `1px solid ${colors.highlight}`, + borderRadius: '10px', + overflow: 'hidden', + img: { + width: '100%', + }, +}); + export default (props: Props) => { const {bookmarks, events, onNavigate, onFavorite} = props; return events.length === 0 ? ( @@ -38,13 +55,28 @@ export default (props: Props) => { {events.map((event: NavigationEvent, idx) => { return ( - + + {event.uri != null ? ( + + {event.screenshot != null ? ( + + ) : ( + + + + )} + + ) : null} + + ); })} diff --git a/src/plugins/navigation/flow-types.js b/src/plugins/navigation/flow-types.js index 9c99bd96e..2a0816915 100644 --- a/src/plugins/navigation/flow-types.js +++ b/src/plugins/navigation/flow-types.js @@ -27,6 +27,7 @@ export type PersistedState = {| export type NavigationEvent = {| date: ?Date, uri: ?URI, + screenshot: ?string, |}; export type Bookmark = {| diff --git a/src/plugins/navigation/index.js b/src/plugins/navigation/index.js index 227f527d0..7fcb8e8bd 100644 --- a/src/plugins/navigation/index.js +++ b/src/plugins/navigation/index.js @@ -6,7 +6,7 @@ * @flow strict-local */ -import {FlipperPlugin} from 'flipper'; +import {FlipperPlugin, bufferToBlob} from 'flipper'; import { BookmarksSidebar, SaveBookmarkDialog, @@ -63,19 +63,6 @@ export default class extends FlipperPlugin { payload: NavigationEvent, ): $Shape => { switch (method) { - case 'nav_event': - return { - ...persistedState, - currentURI: - payload.uri == null ? persistedState.currentURI : payload.uri, - navigationEvents: [ - { - uri: payload.uri === undefined ? null : payload.uri, - date: payload.date || new Date(), - }, - ...persistedState.navigationEvents, - ], - }; default: return { ...persistedState, @@ -83,8 +70,38 @@ export default class extends FlipperPlugin { } }; + subscribeToNavigationEvents = () => { + this.client.subscribe('nav_event', payload => { + let {persistedState} = this.props; + const {setPersistedState} = this.props; + const navigationEvent: NavigationEvent = { + uri: payload.uri === undefined ? null : payload.uri, + date: payload.date || new Date(), + screenshot: null, + }; + setPersistedState({ + ...persistedState, + currentURI: + payload.uri == null ? persistedState.currentURI : payload.uri, + navigationEvents: [navigationEvent, ...persistedState.navigationEvents], + }); + // Wait for view to render and then take a screenshot + setTimeout(() => { + persistedState = this.props.persistedState; + this.getDevice() + .then(device => device.screenshot()) + .then((buffer: Buffer) => { + const blobURL = URL.createObjectURL(bufferToBlob(buffer)); + navigationEvent.screenshot = blobURL; + setPersistedState({...persistedState}); + }); + }, 1000); + }); + }; + componentDidMount = () => { const {selectedApp} = this.props; + this.subscribeToNavigationEvents(); getAppMatchPatterns(selectedApp) .then(patterns => { this.props.setPersistedState({