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
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {styled} from 'flipper';
|
import {
|
||||||
import {parseURIParameters} from '../util/uri';
|
styled,
|
||||||
import IconButton from './IconButton';
|
colors,
|
||||||
import FavoriteButton from './FavoriteButton';
|
ManagedTable,
|
||||||
|
TableBodyRow,
|
||||||
|
FlexCenter,
|
||||||
|
LoadingIndicator,
|
||||||
|
Button,
|
||||||
|
Glyph,
|
||||||
|
} from 'flipper';
|
||||||
|
import {parseURIParameters, stripQueryParameters} from '../util/uri';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
const BOX_HEIGHT = 240;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isBookmarked: boolean;
|
isBookmarked: boolean;
|
||||||
uri: string | null;
|
uri: string | null;
|
||||||
className: string | null;
|
className: string | null;
|
||||||
onNavigate: (query: string) => void;
|
onNavigate: (query: string) => void;
|
||||||
onFavorite: (query: string) => void;
|
onFavorite: (query: string) => void;
|
||||||
|
screenshot: string | null;
|
||||||
|
date: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavigationInfoBoxContainer = styled('div')({
|
const ScreenshotContainer = styled('div')({
|
||||||
backgroundColor: '#FDFDEA',
|
width: 200,
|
||||||
maxWidth: 500,
|
minWidth: 200,
|
||||||
height: 'fit-content',
|
overflow: 'hidden',
|
||||||
padding: 20,
|
borderLeft: `1px ${colors.blueGreyTint90} solid`,
|
||||||
borderRadius: 10,
|
|
||||||
margin: 20,
|
|
||||||
width: 'fit-content',
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
'.nav-info-text': {
|
img: {
|
||||||
color: '#707070',
|
width: '100%',
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) => {
|
export default (props: Props) => {
|
||||||
const {uri, isBookmarked, className} = props;
|
const {
|
||||||
|
uri,
|
||||||
|
isBookmarked,
|
||||||
|
className,
|
||||||
|
screenshot,
|
||||||
|
onNavigate,
|
||||||
|
onFavorite,
|
||||||
|
date,
|
||||||
|
} = props;
|
||||||
if (uri == null && className == null) {
|
if (uri == null && className == null) {
|
||||||
return (
|
return <NoData>Unknown Navigation Event</NoData>;
|
||||||
<NavigationInfoBoxContainer>
|
|
||||||
<div className="nav-info-text">View has no URI information</div>
|
|
||||||
</NavigationInfoBoxContainer>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
const parameters = uri != null ? parseURIParameters(uri) : null;
|
const parameters = uri != null ? parseURIParameters(uri) : null;
|
||||||
return (
|
return (
|
||||||
<NavigationInfoBoxContainer>
|
<NavigationInfoBoxContainer>
|
||||||
{uri != null ? (
|
<NavigationDataContainer>
|
||||||
<>
|
<Header>
|
||||||
<div className="icon-container">
|
{uri != null ? stripQueryParameters(uri) : ''}
|
||||||
<FavoriteButton
|
<Seperator />
|
||||||
highlighted={isBookmarked}
|
{className != null ? (
|
||||||
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 ? (
|
|
||||||
<>
|
<>
|
||||||
<div className="nav-info-text bold">parameters:</div>
|
<Glyph
|
||||||
{Array.from(parameters, ([key, value]) => (
|
color={colors.light30}
|
||||||
<div key={key} className="nav-info-text selectable">
|
size={16}
|
||||||
{key}
|
name="paper-fold-text"
|
||||||
{value ? `: ${value}` : ''}
|
/>
|
||||||
</div>
|
|
||||||
))}
|
<ClassNameContainer>
|
||||||
|
{className != null ? className : ''}
|
||||||
|
</ClassNameContainer>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</Header>
|
||||||
) : null}
|
<ParametersContainer>
|
||||||
{className != null ? (
|
{parameters != null && parameters.size > 0 ? (
|
||||||
<>
|
buildParameterTable(parameters)
|
||||||
<div className="nav-info-text bold">Class Name:</div>
|
) : (
|
||||||
<div className="nav-info-text selectable">{className}</div>
|
<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}
|
) : null}
|
||||||
</NavigationInfoBoxContainer>
|
</NavigationInfoBoxContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {colors, FlexCenter, styled, LoadingIndicator} from 'flipper';
|
import {colors, FlexCenter, styled} from 'flipper';
|
||||||
import {NavigationInfoBox} from './';
|
import {NavigationInfoBox} from './';
|
||||||
import {Bookmark, NavigationEvent, URI} from '../types';
|
import {Bookmark, NavigationEvent, URI} from '../types';
|
||||||
import React from 'react';
|
import React, {useRef} from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
bookmarks: Map<string, Bookmark>;
|
bookmarks: Map<string, Bookmark>;
|
||||||
@@ -20,6 +20,8 @@ type Props = {
|
|||||||
const TimelineContainer = styled('div')({
|
const TimelineContainer = styled('div')({
|
||||||
overflowY: 'scroll',
|
overflowY: 'scroll',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
|
backgroundColor: colors.light02,
|
||||||
|
scrollBehavior: 'smooth',
|
||||||
});
|
});
|
||||||
|
|
||||||
const NavigationEventContainer = styled('div')({
|
const NavigationEventContainer = styled('div')({
|
||||||
@@ -34,38 +36,16 @@ const NoData = styled(FlexCenter)({
|
|||||||
color: colors.macOSTitleBarIcon,
|
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) => {
|
export default (props: Props) => {
|
||||||
const {bookmarks, events, onNavigate, onFavorite} = props;
|
const {bookmarks, events, onNavigate, onFavorite} = props;
|
||||||
|
const timelineRef = useRef<HTMLDivElement>(null);
|
||||||
return events.length === 0 ? (
|
return events.length === 0 ? (
|
||||||
<NoData>No Navigation Events to Show</NoData>
|
<NoData>No Navigation Events to Show</NoData>
|
||||||
) : (
|
) : (
|
||||||
<TimelineContainer>
|
<TimelineContainer innerRef={timelineRef}>
|
||||||
{events.map((event: NavigationEvent, idx: number) => {
|
{events.map((event: NavigationEvent, idx: number) => {
|
||||||
return (
|
return (
|
||||||
<NavigationEventContainer>
|
<NavigationEventContainer>
|
||||||
{event.uri != null || event.className != null ? (
|
|
||||||
<ScreenshotContainer>
|
|
||||||
{event.screenshot != null ? (
|
|
||||||
<img src={event.screenshot} />
|
|
||||||
) : (
|
|
||||||
<FlexCenter grow>
|
|
||||||
<LoadingIndicator size={32} />
|
|
||||||
</FlexCenter>
|
|
||||||
)}
|
|
||||||
</ScreenshotContainer>
|
|
||||||
) : null}
|
|
||||||
<NavigationInfoBox
|
<NavigationInfoBox
|
||||||
key={idx}
|
key={idx}
|
||||||
isBookmarked={
|
isBookmarked={
|
||||||
@@ -73,8 +53,15 @@ export default (props: Props) => {
|
|||||||
}
|
}
|
||||||
className={event.className}
|
className={event.className}
|
||||||
uri={event.uri}
|
uri={event.uri}
|
||||||
onNavigate={onNavigate}
|
onNavigate={uri => {
|
||||||
|
if (timelineRef.current != null) {
|
||||||
|
timelineRef.current.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
onNavigate(uri);
|
||||||
|
}}
|
||||||
onFavorite={onFavorite}
|
onFavorite={onFavorite}
|
||||||
|
screenshot={event.screenshot}
|
||||||
|
date={event.date}
|
||||||
/>
|
/>
|
||||||
</NavigationEventContainer>
|
</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