Plugin folders re-structuring

Summary:
Here I'm changing plugin repository structure to allow re-using of shared packages between both public and fb-internal plugins, and to ensure that public plugins has their own yarn.lock as this will be required to implement reproducible jobs checking plugin compatibility with released flipper versions.

Please note that there are a lot of moved files in this diff, make sure to click "Expand all" to see all that actually changed (there are not much of them actually).

New proposed structure for plugin packages:
```
- root
- node_modules - modules included into Flipper: flipper, flipper-plugin, react, antd, emotion
-- plugins
 --- node_modules - modules used by both public and fb-internal plugins (shared libs will be linked here, see D27034936)
 --- public
---- node_modules - modules used by public plugins
---- pluginA
----- node_modules - modules used by plugin A exclusively
---- pluginB
----- node_modules - modules used by plugin B exclusively
 --- fb
---- node_modules - modules used by fb-internal plugins
---- pluginC
----- node_modules - modules used by plugin C exclusively
---- pluginD
----- node_modules - modules used by plugin D exclusively
```
I've moved all public plugins under dir "plugins/public" and excluded them from root yarn workspaces. Instead, they will have their own yarn workspaces config and yarn.lock and they will use flipper modules as peer dependencies.

Reviewed By: mweststrate

Differential Revision: D27034108

fbshipit-source-id: c2310e3c5bfe7526033f51b46c0ae40199fd7586
This commit is contained in:
Anton Nikolaev
2021-04-09 05:15:14 -07:00
committed by Facebook GitHub Bot
parent 32bf4c32c2
commit b3274a8450
137 changed files with 2133 additions and 371 deletions

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Glyph, styled} from 'flipper';
import {useItemNavigation} from '../hooks/autoCompleteSheet';
import {filterProvidersToLineItems} from '../util/autoCompleteProvider';
import {AutoCompleteProvider, AutoCompleteLineItem, URI} from '../types';
import React from 'react';
type Props = {
providers: Array<AutoCompleteProvider>;
onHighlighted: (uri: URI) => void;
onNavigate: (uri: URI) => void;
query: string;
};
const MAX_ITEMS = 5;
const AutoCompleteSheetContainer = styled.div({
width: '100%',
position: 'absolute',
top: 'calc(100% - 3px)',
backgroundColor: 'white',
zIndex: 1,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
});
const SheetItem = styled.div({
padding: 5,
textOverflow: 'ellipsis',
overflowX: 'hidden',
whiteSpace: 'nowrap',
'&.selected': {
backgroundColor: 'rgba(155, 155, 155, 0.2)',
},
'&:hover': {
backgroundColor: 'rgba(155, 155, 155, 0.2)',
},
});
const SheetItemIcon = styled.span({
padding: 8,
});
export default (props: Props) => {
const {providers, onHighlighted, onNavigate, query} = props;
const lineItems = filterProvidersToLineItems(providers, query, MAX_ITEMS);
lineItems.unshift({uri: query, matchPattern: query, icon: 'send'});
const selectedItem = useItemNavigation(lineItems, onHighlighted);
return (
<AutoCompleteSheetContainer>
{lineItems.map((lineItem: AutoCompleteLineItem, idx: number) => (
<SheetItem
className={idx === selectedItem ? 'selected' : ''}
key={idx}
onMouseDown={() => onNavigate(lineItem.uri)}>
<SheetItemIcon>
<Glyph name={lineItem.icon} size={16} variant="outline" />
</SheetItemIcon>
{lineItem.matchPattern}
</SheetItem>
))}
</AutoCompleteSheetContainer>
);
};

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {
DetailSidebar,
FlexCenter,
styled,
colors,
FlexRow,
FlexColumn,
Text,
Panel,
} from 'flipper';
import {Bookmark, URI} from '../types';
import {IconButton} from './';
import React from 'react';
type Props = {
bookmarks: Map<string, Bookmark>;
onNavigate: (uri: URI) => void;
onRemove: (uri: URI) => void;
};
const NoData = styled(FlexCenter)({
fontSize: 18,
color: colors.macOSTitleBarIcon,
});
const BookmarksList = styled.div({
overflowY: 'scroll',
overflowX: 'hidden',
height: '100%',
backgroundColor: colors.white,
});
const BookmarkContainer = styled(FlexRow)({
width: '100%',
padding: 10,
height: 55,
alignItems: 'center',
cursor: 'pointer',
borderBottom: `1px ${colors.greyTint} solid`,
':last-child': {
borderBottom: '0',
},
':active': {
backgroundColor: colors.highlight,
color: colors.white,
},
':active *': {
color: colors.white,
},
});
const BookmarkTitle = styled(Text)({
fontSize: '1.1em',
overflowX: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
fontWeight: 500,
});
const BookmarkSubtitle = styled(Text)({
overflowX: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
color: colors.greyTint3,
marginTop: 4,
});
const TextContainer = styled(FlexColumn)({
justifyContent: 'center',
});
const alphabetizeBookmarkCompare = (b1: Bookmark, b2: Bookmark) => {
return b1.uri < b2.uri ? -1 : b1.uri > b2.uri ? 1 : 0;
};
export default (props: Props) => {
const {bookmarks, onNavigate, onRemove} = props;
return (
<DetailSidebar>
<Panel heading="Bookmarks" floating={false} padded={false}>
{bookmarks.size === 0 ? (
<NoData grow>No Bookmarks</NoData>
) : (
<BookmarksList>
{[...bookmarks.values()]
.sort(alphabetizeBookmarkCompare)
.map((bookmark, idx) => (
<BookmarkContainer
key={idx}
role="button"
tabIndex={0}
onClick={() => {
onNavigate(bookmark.uri);
}}>
<TextContainer grow>
<BookmarkTitle>
{bookmark.commonName || bookmark.uri}
</BookmarkTitle>
{!bookmark.commonName && (
<BookmarkSubtitle>{bookmark.uri}</BookmarkSubtitle>
)}
</TextContainer>
<IconButton
color={colors.macOSTitleBarButtonBackgroundActive}
outline={false}
icon="cross-circle"
size={16}
onClick={() => onRemove(bookmark.uri)}
/>
</BookmarkContainer>
))}
</BookmarksList>
)}
</Panel>
</DetailSidebar>
);
};

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {styled, IconSize, colors} from 'flipper';
import {IconButton} from './';
import React from 'react';
type Props = {
onClick?: () => void;
highlighted: boolean;
size: IconSize;
};
const FavoriteButtonContainer = styled.div({
position: 'relative',
'>:first-child': {
position: 'absolute',
},
'>:last-child': {
position: 'relative',
},
});
export default (props: Props) => {
const {highlighted, onClick, ...iconButtonProps} = props;
return (
<FavoriteButtonContainer>
{highlighted ? (
<IconButton
outline={false}
color={colors.lemon}
icon="star"
{...iconButtonProps}
/>
) : null}
<IconButton
outline={true}
icon="star"
onClick={onClick}
{...iconButtonProps}
/>
</FavoriteButtonContainer>
);
};

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Glyph, styled, keyframes, IconSize} from 'flipper';
import React from 'react';
const shrinkAnimation = keyframes({
'0%': {
transform: 'scale(1);',
},
'100%': {
transform: 'scale(.9)',
},
});
type Props = {
icon: string;
outline?: boolean;
onClick?: () => void;
color?: string;
size: IconSize;
};
const RippleEffect = styled.div({
padding: 5,
borderRadius: 100,
backgroundPosition: 'center',
transition: 'background 0.5s',
':hover': {
background:
'rgba(155, 155, 155, 0.2) radial-gradient(circle, transparent 1%, rgba(155, 155, 155, 0.2) 1%) center/15000%',
},
':active': {
backgroundColor: 'rgba(201, 200, 200, 0.5)',
backgroundSize: '100%',
transition: 'background 0s',
},
});
const IconButton = styled.div({
':active': {
animation: `${shrinkAnimation} .25s ease forwards`,
},
});
export default function (props: Props) {
return (
<RippleEffect>
<IconButton className="icon-button" onClick={props.onClick}>
<Glyph
name={props.icon}
size={props.size}
color={props.color}
variant={props.outline ? 'outline' : 'filled'}
/>
</IconButton>
</RippleEffect>
);
}

View File

@@ -0,0 +1,240 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {
styled,
colors,
ManagedTable,
TableBodyRow,
FlexCenter,
LoadingIndicator,
Button,
Glyph,
} from 'flipper';
import {parseURIParameters, stripQueryParameters} from '../util/uri';
import React from 'react';
const BOX_HEIGHT = 240;
type Props = {
isBookmarked: boolean;
uri: string | null;
className: string | null;
onNavigate: (query: string) => void;
onFavorite: (query: string) => void;
screenshot: string | null;
date: Date | null;
};
const ScreenshotContainer = styled.div({
width: 200,
minWidth: 200,
overflow: 'hidden',
borderLeft: `1px ${colors.blueGreyTint90} solid`,
position: 'relative',
height: '100%',
borderRadius: 10,
img: {
width: '100%',
},
});
const NoData = styled.div({
color: colors.light30,
fontSize: 14,
position: 'relative',
});
const NavigationDataContainer = styled.div({
alignItems: 'flex-start',
flexGrow: 1,
position: 'relative',
});
const Footer = styled.div({
width: '100%',
padding: '10px',
borderTop: `1px ${colors.blueGreyTint90} solid`,
display: 'flex',
alignItems: 'center',
});
const Seperator = styled.div({
flexGrow: 1,
});
const TimeContainer = styled.div({
color: colors.light30,
fontSize: 14,
});
const NavigationInfoBoxContainer = styled.div({
display: 'flex',
height: BOX_HEIGHT,
borderRadius: 10,
flexGrow: 1,
position: 'relative',
marginBottom: 10,
backgroundColor: colors.white,
boxShadow: '1px 1px 5px rgba(0,0,0,0.1)',
});
const Header = styled.div({
fontSize: 18,
fontWeight: 500,
userSelect: 'text',
cursor: 'text',
padding: 10,
borderBottom: `1px ${colors.blueGreyTint90} solid`,
display: 'flex',
});
const ClassNameContainer = styled.div({
color: colors.light30,
});
const ParametersContainer = styled.div({
height: 150,
'&>*': {
height: 150,
marginBottom: 20,
},
});
const NoParamters = styled(FlexCenter)({
fontSize: 18,
color: colors.light10,
});
const TimelineCircle = styled.div({
width: 18,
height: 18,
top: 11,
left: -33,
backgroundColor: colors.light02,
border: `4px solid ${colors.highlight}`,
borderRadius: '50%',
position: 'absolute',
});
const TimelineMiniCircle = styled.div({
width: 12,
height: 12,
top: 1,
left: -30,
borderRadius: '50%',
backgroundColor: colors.highlight,
position: 'absolute',
});
const buildParameterTable = (parameters: Map<string, string>) => {
const tableRows: Array<TableBodyRow> = [];
let idx = 0;
parameters.forEach((parameter_value, parameter) => {
tableRows.push({
key: idx.toString(),
columns: {
parameter: {
value: parameter,
},
value: {
value: parameter_value,
},
},
});
idx++;
});
return (
<ManagedTable
columns={{parameter: {value: 'Parameter'}, value: {value: 'Value'}}}
rows={tableRows}
zebra={false}
/>
);
};
export default (props: Props) => {
const {
uri,
isBookmarked,
className,
screenshot,
onNavigate,
onFavorite,
date,
} = props;
if (uri == null && className == null) {
return (
<>
<NoData>
<TimelineMiniCircle />
Unknown Navigation Event
</NoData>
</>
);
} else {
const parameters = uri != null ? parseURIParameters(uri) : null;
return (
<NavigationInfoBoxContainer>
<TimelineCircle />
<NavigationDataContainer>
<Header>
{uri != null ? stripQueryParameters(uri) : ''}
<Seperator />
{className != null ? (
<>
<Glyph
color={colors.light30}
size={16}
name="paper-fold-text"
/>
&nbsp;
<ClassNameContainer>
{className != null ? className : ''}
</ClassNameContainer>
</>
) : null}
</Header>
<ParametersContainer>
{parameters != null && parameters.size > 0 ? (
buildParameterTable(parameters)
) : (
<NoParamters grow>No Parameters for this Event</NoParamters>
)}
</ParametersContainer>
<Footer>
{uri != null ? (
<>
<Button onClick={() => onNavigate(uri)}>Open</Button>
<Button onClick={() => onFavorite(uri)}>
{isBookmarked ? 'Edit Bookmark' : 'Bookmark'}
</Button>
</>
) : null}
<Seperator />
<TimeContainer>
{date != null ? date.toTimeString() : ''}
</TimeContainer>
</Footer>
</NavigationDataContainer>
{uri != null || className != null ? (
<ScreenshotContainer>
{screenshot != null ? (
<img src={screenshot} />
) : (
<FlexCenter grow>
<LoadingIndicator size={32} />
</FlexCenter>
)}
</ScreenshotContainer>
) : null}
</NavigationInfoBoxContainer>
);
}
};

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Modal, Button, Alert, Input, Typography} from 'antd';
import {Layout} from 'flipper-plugin';
import {
replaceRequiredParametersWithValues,
parameterIsNumberType,
parameterIsBooleanType,
validateParameter,
liveEdit,
} from '../util/uri';
import {useRequiredParameterFormValidator} from '../hooks/requiredParameters';
import React from 'react';
import {URI} from '../types';
type Props = {
uri: string;
requiredParameters: Array<string>;
onHide: () => void;
onSubmit: (uri: URI) => void;
};
export default (props: Props) => {
const {onHide, onSubmit, uri, requiredParameters} = props;
const {isValid, values, setValuesArray} = useRequiredParameterFormValidator(
requiredParameters,
);
return (
<Modal
visible
onCancel={onHide}
title="Provide bookmark details"
footer={
<>
<Button
onClick={() => {
onHide();
setValuesArray([]);
}}>
Cancel
</Button>
<Button
type={'primary'}
onClick={() => {
onSubmit(replaceRequiredParametersWithValues(uri, values));
onHide();
}}
disabled={!isValid}>
Submit
</Button>
</>
}>
<Layout.Container gap>
<Alert
type="info"
message="This uri has required parameters denoted by '{parameter}'}."
/>
{requiredParameters.map((paramater, idx) => (
<div key={idx}>
<Input
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setValuesArray([
...values.slice(0, idx),
event.target.value,
...values.slice(idx + 1),
])
}
name={paramater}
placeholder={paramater}
/>
{values[idx] &&
parameterIsNumberType(paramater) &&
!validateParameter(values[idx], paramater) ? (
<Alert type="error" message="Parameter must be a number" />
) : null}
{values[idx] &&
parameterIsBooleanType(paramater) &&
!validateParameter(values[idx], paramater) ? (
<Alert
type="error"
message="Parameter must be either 'true' or 'false'"
/>
) : null}
</div>
))}
<Typography.Text code>{liveEdit(uri, values)}</Typography.Text>
</Layout.Container>
</Modal>
);
};

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Button, FlexColumn, Input, Sheet, styled} from 'flipper';
import React, {useState} from 'react';
import {Bookmark, URI} from '../types';
type Props = {
uri: string | null;
edit: boolean;
shouldShow: boolean;
onHide?: () => void;
onRemove: (uri: URI) => void;
onSubmit: (bookmark: Bookmark) => void;
};
const Container = styled(FlexColumn)({
padding: 10,
width: 400,
});
const Title = styled.div({
fontWeight: 500,
marginTop: 8,
marginLeft: 2,
marginBottom: 8,
});
const URIContainer = styled.div({
marginLeft: 2,
marginBottom: 8,
overflowWrap: 'break-word',
});
const ButtonContainer = styled.div({
marginLeft: 'auto',
});
const NameInput = styled(Input)({
margin: 0,
marginBottom: 10,
height: 30,
});
export default (props: Props) => {
const {edit, shouldShow, onHide, onRemove, onSubmit, uri} = props;
const [commonName, setCommonName] = useState('');
if (uri == null || !shouldShow) {
return null;
} else {
return (
<Sheet onHideSheet={onHide}>
{(onHide: () => void) => {
return (
<Container>
<Title>
{edit ? 'Edit bookmark...' : 'Save to bookmarks...'}
</Title>
<NameInput
placeholder="Name..."
value={commonName}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setCommonName(event.target.value)
}
/>
<URIContainer>{uri}</URIContainer>
<ButtonContainer>
<Button
onClick={() => {
onHide();
setCommonName('');
}}
compact
padded>
Cancel
</Button>
{edit ? (
<Button
type="danger"
onClick={() => {
onHide();
onRemove(uri);
setCommonName('');
}}
compact
padded>
Remove
</Button>
) : null}
<Button
type="primary"
onClick={() => {
onHide();
onSubmit({uri, commonName});
// The component state is remembered even after unmounting.
// Thus it is necessary to reset the commonName here.
setCommonName('');
}}
compact
padded>
Save
</Button>
</ButtonContainer>
</Container>
);
}}
</Sheet>
);
}
};

View File

@@ -0,0 +1,166 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {styled, SearchBox, SearchInput, Toolbar} from 'flipper';
import {AutoCompleteSheet, IconButton, FavoriteButton} from './';
import {AutoCompleteProvider, Bookmark, URI} from '../types';
import React, {Component} from 'react';
type Props = {
onFavorite: (query: URI) => void;
onNavigate: (query: URI) => void;
bookmarks: Map<URI, Bookmark>;
providers: Array<AutoCompleteProvider>;
uriFromAbove: URI;
};
type State = {
query: URI;
inputFocused: boolean;
autoCompleteSheetOpen: boolean;
searchInputValue: URI;
prevURIFromAbove: URI;
};
const IconContainer = styled.div({
display: 'inline-flex',
height: '16px',
alignItems: 'center',
'': {
marginLeft: 10,
'.icon-button': {
height: 16,
},
'img,div': {
verticalAlign: 'top',
alignItems: 'none',
},
},
});
const ToolbarContainer = styled.div({
'.drop-shadow': {
boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
},
});
const SearchInputContainer = styled.div({
width: '100%',
marginLeft: 5,
marginRight: 9,
position: 'relative',
});
class SearchBar extends Component<Props, State> {
state = {
inputFocused: false,
autoCompleteSheetOpen: false,
query: '',
searchInputValue: '',
prevURIFromAbove: '',
};
favorite = (searchInputValue: string) => {
this.props.onFavorite(searchInputValue);
};
navigateTo = (searchInputValue: string) => {
this.setState({query: searchInputValue, searchInputValue});
this.props.onNavigate(searchInputValue);
};
queryInputChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
this.setState({query: value, searchInputValue: value});
};
static getDerivedStateFromProps = (newProps: Props, state: State) => {
const {uriFromAbove: newURIFromAbove} = newProps;
const {prevURIFromAbove} = state;
if (newURIFromAbove !== prevURIFromAbove) {
return {
searchInputValue: newURIFromAbove,
query: newURIFromAbove,
prevURIFromAbove: newURIFromAbove,
};
}
return null;
};
render() {
const {bookmarks, providers} = this.props;
const {
autoCompleteSheetOpen,
inputFocused,
searchInputValue,
query,
} = this.state;
return (
<ToolbarContainer>
<Toolbar>
<SearchBox className={inputFocused ? 'drop-shadow' : ''}>
<SearchInputContainer>
<SearchInput
value={searchInputValue}
onBlur={() =>
this.setState({
autoCompleteSheetOpen: false,
inputFocused: false,
})
}
onFocus={(event: React.FocusEvent<HTMLInputElement>) => {
event.target.select();
this.setState({
autoCompleteSheetOpen: true,
inputFocused: true,
});
}}
onChange={this.queryInputChanged}
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
this.navigateTo(this.state.searchInputValue);
(e.target as HTMLInputElement).blur();
}
}}
placeholder="Navigate To..."
/>
{autoCompleteSheetOpen && query.length > 0 ? (
<AutoCompleteSheet
providers={providers}
onNavigate={this.navigateTo}
onHighlighted={(newInputValue: URI) =>
this.setState({searchInputValue: newInputValue})
}
query={query}
/>
) : null}
</SearchInputContainer>
</SearchBox>
{searchInputValue.length > 0 ? (
<IconContainer>
<IconButton
icon="send"
size={16}
outline={true}
onClick={() => this.navigateTo(searchInputValue)}
/>
<FavoriteButton
size={16}
highlighted={bookmarks.has(searchInputValue)}
onClick={() => this.favorite(searchInputValue)}
/>
</IconContainer>
) : null}
</Toolbar>
</ToolbarContainer>
);
}
}
export default SearchBar;

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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, FlexCenter, styled} from 'flipper';
import {NavigationInfoBox} from './';
import {Bookmark, NavigationEvent, URI} from '../types';
import React, {useRef} from 'react';
type Props = {
bookmarks: Map<string, Bookmark>;
events: Array<NavigationEvent>;
onNavigate: (uri: URI) => void;
onFavorite: (uri: URI) => void;
};
const TimelineLine = styled.div({
width: 2,
backgroundColor: colors.highlight,
position: 'absolute',
top: 38,
bottom: 0,
});
const TimelineContainer = styled.div({
position: 'relative',
paddingLeft: 25,
overflowY: 'scroll',
flexGrow: 1,
backgroundColor: colors.light02,
scrollBehavior: 'smooth',
'&>div': {
position: 'relative',
minHeight: '100%',
'&:last-child': {
paddingBottom: 25,
},
},
});
const NavigationEventContainer = styled.div({
display: 'flex',
paddingTop: 25,
paddingLeft: 25,
marginRight: 25,
});
const NoData = styled(FlexCenter)({
height: '100%',
fontSize: 18,
backgroundColor: colors.macOSTitleBarBackgroundBlur,
color: colors.macOSTitleBarIcon,
});
export default (props: Props) => {
const {bookmarks, events, onNavigate, onFavorite} = props;
const timelineRef = useRef<HTMLDivElement>(null);
return events.length === 0 ? (
<NoData>No Navigation Events to Show</NoData>
) : (
<TimelineContainer ref={timelineRef}>
<div>
<TimelineLine />
{events.map((event: NavigationEvent, idx: number) => {
return (
<NavigationEventContainer key={idx}>
<NavigationInfoBox
isBookmarked={
event.uri != null ? bookmarks.has(event.uri) : false
}
className={event.className}
uri={event.uri}
onNavigate={(uri) => {
if (timelineRef.current != null) {
timelineRef.current.scrollTo(0, 0);
}
onNavigate(uri);
}}
onFavorite={onFavorite}
screenshot={event.screenshot}
date={event.date}
/>
</NavigationEventContainer>
);
})}
</div>
</TimelineContainer>
);
};

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
export {default as AutoCompleteSheet} from './AutoCompleteSheet';
export {default as BookmarksSidebar} from './BookmarksSidebar';
export {default as FavoriteButton} from './FavoriteButton';
export {default as IconButton} from './IconButton';
export {default as NavigationInfoBox} from './NavigationInfoBox';
export {default as RequiredParametersDialog} from './RequiredParametersDialog';
export {default as SaveBookmarkDialog} from './SaveBookmarkDialog';
export {default as SearchBar} from './SearchBar';
export {default as Timeline} from './Timeline';