Navigation Timeline UI overhaul.
Summary: This is a UI ovehaul for the Navigation plugin, taking inspiration from the Notifications page in Flipper. We now display a timestamp, open page and bookmark are more clearly identified, screenshots are organized more neatly, and parameters are displayed in a table. If the class name of the ViewController is available, that will also be displayed. Edit: Adding in some of the requested changes. Improved UI: https://pxl.cl/K0h9 Scroll on opening a page: https://pxl.cl/K0hQ Reviewed By: danielbuechele Differential Revision: D17161734 fbshipit-source-id: e5e054bf87f540964e90da3a798fd0c23df86540
This commit is contained in:
committed by
Facebook Github Bot
parent
61c033daaf
commit
df667027df
@@ -5,101 +5,200 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {styled} from 'flipper';
|
||||
import {parseURIParameters} from '../util/uri';
|
||||
import IconButton from './IconButton';
|
||||
import FavoriteButton from './FavoriteButton';
|
||||
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 NavigationInfoBoxContainer = styled('div')({
|
||||
backgroundColor: '#FDFDEA',
|
||||
maxWidth: 500,
|
||||
height: 'fit-content',
|
||||
padding: 20,
|
||||
borderRadius: 10,
|
||||
margin: 20,
|
||||
width: 'fit-content',
|
||||
const ScreenshotContainer = styled('div')({
|
||||
width: 200,
|
||||
minWidth: 200,
|
||||
overflow: 'hidden',
|
||||
borderLeft: `1px ${colors.blueGreyTint90} solid`,
|
||||
position: 'relative',
|
||||
'.nav-info-text': {
|
||||
color: '#707070',
|
||||
fontSize: '1.2em',
|
||||
lineHeight: '1.25em',
|
||||
wordWrap: 'break-word',
|
||||
},
|
||||
'.nav-info-text.bold': {
|
||||
fontWeight: 'bold',
|
||||
marginTop: '10px',
|
||||
},
|
||||
'.nav-info-text.selectable': {
|
||||
userSelect: 'text',
|
||||
cursor: 'text',
|
||||
},
|
||||
'.icon-container': {
|
||||
display: 'inline-flex',
|
||||
padding: 5,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
'>*': {
|
||||
marginRight: 2,
|
||||
},
|
||||
img: {
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
const NoData = styled('div')({
|
||||
color: colors.light30,
|
||||
fontSize: 14,
|
||||
});
|
||||
|
||||
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,
|
||||
overflow: 'hidden',
|
||||
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 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} = props;
|
||||
const {
|
||||
uri,
|
||||
isBookmarked,
|
||||
className,
|
||||
screenshot,
|
||||
onNavigate,
|
||||
onFavorite,
|
||||
date,
|
||||
} = props;
|
||||
if (uri == null && className == null) {
|
||||
return (
|
||||
<NavigationInfoBoxContainer>
|
||||
<div className="nav-info-text">View has no URI information</div>
|
||||
</NavigationInfoBoxContainer>
|
||||
);
|
||||
return <NoData>Unknown Navigation Event</NoData>;
|
||||
} else {
|
||||
const parameters = uri != null ? parseURIParameters(uri) : null;
|
||||
return (
|
||||
<NavigationInfoBoxContainer>
|
||||
{uri != null ? (
|
||||
<>
|
||||
<div className="icon-container">
|
||||
<FavoriteButton
|
||||
highlighted={isBookmarked}
|
||||
size={16}
|
||||
onClick={() => props.onFavorite(uri)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="eye"
|
||||
size={16}
|
||||
onClick={() => props.onNavigate(uri)}
|
||||
/>
|
||||
</div>
|
||||
<div className="nav-info-text bold">uri:</div>
|
||||
<div className="nav-info-text selectable">{uri}</div>
|
||||
{parameters != null && parameters.size > 0 ? (
|
||||
<NavigationDataContainer>
|
||||
<Header>
|
||||
{uri != null ? stripQueryParameters(uri) : ''}
|
||||
<Seperator />
|
||||
{className != null ? (
|
||||
<>
|
||||
<div className="nav-info-text bold">parameters:</div>
|
||||
{Array.from(parameters, ([key, value]) => (
|
||||
<div key={key} className="nav-info-text selectable">
|
||||
{key}
|
||||
{value ? `: ${value}` : ''}
|
||||
</div>
|
||||
))}
|
||||
<Glyph
|
||||
color={colors.light30}
|
||||
size={16}
|
||||
name="paper-fold-text"
|
||||
/>
|
||||
|
||||
<ClassNameContainer>
|
||||
{className != null ? className : ''}
|
||||
</ClassNameContainer>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
{className != null ? (
|
||||
<>
|
||||
<div className="nav-info-text bold">Class Name:</div>
|
||||
<div className="nav-info-text selectable">{className}</div>
|
||||
</>
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {colors, FlexCenter, styled, LoadingIndicator} from 'flipper';
|
||||
import {colors, FlexCenter, styled} from 'flipper';
|
||||
import {NavigationInfoBox} from './';
|
||||
import {Bookmark, NavigationEvent, URI} from '../types';
|
||||
import React from 'react';
|
||||
import React, {useRef} from 'react';
|
||||
|
||||
type Props = {
|
||||
bookmarks: Map<string, Bookmark>;
|
||||
@@ -20,6 +20,8 @@ type Props = {
|
||||
const TimelineContainer = styled('div')({
|
||||
overflowY: 'scroll',
|
||||
flexGrow: 1,
|
||||
backgroundColor: colors.light02,
|
||||
scrollBehavior: 'smooth',
|
||||
});
|
||||
|
||||
const NavigationEventContainer = styled('div')({
|
||||
@@ -34,38 +36,16 @@ 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;
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
return events.length === 0 ? (
|
||||
<NoData>No Navigation Events to Show</NoData>
|
||||
) : (
|
||||
<TimelineContainer>
|
||||
<TimelineContainer innerRef={timelineRef}>
|
||||
{events.map((event: NavigationEvent, idx: number) => {
|
||||
return (
|
||||
<NavigationEventContainer>
|
||||
{event.uri != null || event.className != null ? (
|
||||
<ScreenshotContainer>
|
||||
{event.screenshot != null ? (
|
||||
<img src={event.screenshot} />
|
||||
) : (
|
||||
<FlexCenter grow>
|
||||
<LoadingIndicator size={32} />
|
||||
</FlexCenter>
|
||||
)}
|
||||
</ScreenshotContainer>
|
||||
) : null}
|
||||
<NavigationInfoBox
|
||||
key={idx}
|
||||
isBookmarked={
|
||||
@@ -73,8 +53,15 @@ export default (props: Props) => {
|
||||
}
|
||||
className={event.className}
|
||||
uri={event.uri}
|
||||
onNavigate={onNavigate}
|
||||
onNavigate={uri => {
|
||||
if (timelineRef.current != null) {
|
||||
timelineRef.current.scrollTo(0, 0);
|
||||
}
|
||||
onNavigate(uri);
|
||||
}}
|
||||
onFavorite={onFavorite}
|
||||
screenshot={event.screenshot}
|
||||
date={event.date}
|
||||
/>
|
||||
</NavigationEventContainer>
|
||||
);
|
||||
|
||||
@@ -86,3 +86,7 @@ export const liveEdit = (uri: string, formValues: Array<string>) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const stripQueryParameters = (uri: string) => {
|
||||
return uri.replace(/\?.*$/g, '');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user