Migrate Images plugin to ant.design
Summary: {gif:xcsasxxe}
Reviewed By: mweststrate
Differential Revision: D28463799
fbshipit-source-id: 280eaaf0ad5858b3507055a278d1f98fd5668fd0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6e206fc054
commit
996e8ab87c
@@ -10,23 +10,22 @@
|
|||||||
import {CacheInfo, ImageId, ImageData, ImagesList} from './api';
|
import {CacheInfo, ImageId, ImageData, ImagesList} from './api';
|
||||||
import {ImageEventWithId} from './index';
|
import {ImageEventWithId} from './index';
|
||||||
|
|
||||||
|
import {styled, Layout, Toolbar, theme} from 'flipper-plugin';
|
||||||
import {
|
import {
|
||||||
Toolbar,
|
|
||||||
Button,
|
Button,
|
||||||
Spacer,
|
Switch,
|
||||||
colors,
|
Empty,
|
||||||
FlexBox,
|
Skeleton,
|
||||||
FlexRow,
|
Typography,
|
||||||
FlexColumn,
|
Image,
|
||||||
LoadingIndicator,
|
Row,
|
||||||
styled,
|
Col,
|
||||||
ToggleButton,
|
Badge,
|
||||||
Text,
|
} from 'antd';
|
||||||
} from 'flipper';
|
|
||||||
import MultipleSelect from './MultipleSelect';
|
import MultipleSelect from './MultipleSelect';
|
||||||
import {ImagesMap} from './ImagePool';
|
import {ImagesMap} from './ImagePool';
|
||||||
import {clipboard} from 'electron';
|
import React, {PureComponent} from 'react';
|
||||||
import React, {ChangeEvent, KeyboardEvent, PureComponent} from 'react';
|
import {DeleteFilled} from '@ant-design/icons';
|
||||||
|
|
||||||
function formatMB(bytes: number) {
|
function formatMB(bytes: number) {
|
||||||
return Math.floor(bytes / (1024 * 1024)) + 'MB';
|
return Math.floor(bytes / (1024 * 1024)) + 'MB';
|
||||||
@@ -42,26 +41,16 @@ type ToggleProps = {
|
|||||||
toggled: boolean;
|
toggled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToolbarToggleButton = styled(ToggleButton)(() => ({
|
|
||||||
alignSelf: 'center',
|
|
||||||
marginRight: 4,
|
|
||||||
minWidth: 30,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ToggleLabel = styled(Text)(() => ({
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}));
|
|
||||||
|
|
||||||
function Toggle(props: ToggleProps) {
|
function Toggle(props: ToggleProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolbarToggleButton
|
<Switch
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onClick && props.onClick(!props.toggled);
|
props.onClick && props.onClick(!props.toggled);
|
||||||
}}
|
}}
|
||||||
toggled={props.toggled}
|
checked={props.toggled}
|
||||||
/>
|
/>
|
||||||
<ToggleLabel>{props.label}</ToggleLabel>
|
<Typography.Text>{props.label}</Typography.Text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -104,39 +93,11 @@ export default class ImagesCacheOverview extends PureComponent<
|
|||||||
size: 150,
|
size: 150,
|
||||||
};
|
};
|
||||||
|
|
||||||
static Container = styled(FlexColumn)({
|
|
||||||
backgroundColor: colors.white,
|
|
||||||
});
|
|
||||||
|
|
||||||
static Content = styled(FlexColumn)({
|
|
||||||
flex: 1,
|
|
||||||
overflow: 'auto',
|
|
||||||
});
|
|
||||||
|
|
||||||
static Empty = styled(FlexBox)({
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '100%',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
|
||||||
});
|
|
||||||
|
|
||||||
onImageSelected = (selectedImage: ImageId) => {
|
onImageSelected = (selectedImage: ImageId) => {
|
||||||
this.setState({selectedImage});
|
this.setState({selectedImage});
|
||||||
this.props.onImageSelected(selectedImage);
|
this.props.onImageSelected(selectedImage);
|
||||||
};
|
};
|
||||||
|
|
||||||
onKeyDown = (e: KeyboardEvent) => {
|
|
||||||
const selectedImage = this.state.selectedImage;
|
|
||||||
const imagesMap = this.props.imagesMap;
|
|
||||||
|
|
||||||
if (selectedImage) {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
|
|
||||||
clipboard.writeText(String(imagesMap[selectedImage]));
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onEnableDebugOverlayToggled = () => {
|
onEnableDebugOverlayToggled = () => {
|
||||||
this.props.onEnableDebugOverlay(!this.props.isDebugOverlayEnabled);
|
this.props.onEnableDebugOverlay(!this.props.isDebugOverlayEnabled);
|
||||||
};
|
};
|
||||||
@@ -145,9 +106,6 @@ export default class ImagesCacheOverview extends PureComponent<
|
|||||||
this.props.onEnableAutoRefresh(!this.props.isAutoRefreshEnabled);
|
this.props.onEnableAutoRefresh(!this.props.isAutoRefreshEnabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeSize = (e: ChangeEvent<HTMLInputElement>) =>
|
|
||||||
this.setState({size: parseInt(e.target.value, 10)});
|
|
||||||
|
|
||||||
onSurfaceOptionsChange = (selectedItem: string, checked: boolean) => {
|
onSurfaceOptionsChange = (selectedItem: string, checked: boolean) => {
|
||||||
const {allSurfacesOption, surfaceOptions} = this.props;
|
const {allSurfacesOption, surfaceOptions} = this.props;
|
||||||
const selectedSurfaces = new Set([...this.props.selectedSurfaces]);
|
const selectedSurfaces = new Set([...this.props.selectedSurfaces]);
|
||||||
@@ -190,12 +148,11 @@ export default class ImagesCacheOverview extends PureComponent<
|
|||||||
) > 0;
|
) > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImagesCacheOverview.Container
|
<Layout.Top>
|
||||||
grow
|
<Toolbar>
|
||||||
onKeyDown={this.onKeyDown}
|
<Button
|
||||||
tabIndex={0}>
|
icon={<DeleteFilled></DeleteFilled>}
|
||||||
<Toolbar position="top">
|
onClick={this.props.onTrimMemory}>
|
||||||
<Button icon="trash" onClick={this.props.onTrimMemory}>
|
|
||||||
Trim Memory
|
Trim Memory
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={this.props.onRefresh}>Refresh</Button>
|
<Button onClick={this.props.onRefresh}>Refresh</Button>
|
||||||
@@ -230,21 +187,13 @@ export default class ImagesCacheOverview extends PureComponent<
|
|||||||
onClick={this.props.onShowDiskImages}
|
onClick={this.props.onShowDiskImages}
|
||||||
label="Show Disk Images"
|
label="Show Disk Images"
|
||||||
/>
|
/>
|
||||||
<Spacer />
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
onChange={this.onChangeSize}
|
|
||||||
min={50}
|
|
||||||
max={150}
|
|
||||||
value={this.state.size}
|
|
||||||
/>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
{!hasImages ? (
|
{!hasImages ? (
|
||||||
<ImagesCacheOverview.Empty>
|
<Layout.Container pad>
|
||||||
<LoadingIndicator size={50} />
|
<Empty />
|
||||||
</ImagesCacheOverview.Empty>
|
</Layout.Container>
|
||||||
) : (
|
) : (
|
||||||
<ImagesCacheOverview.Content>
|
<Layout.ScrollContainer>
|
||||||
{this.props.images.map((data: CacheInfo, index: number) => {
|
{this.props.images.map((data: CacheInfo, index: number) => {
|
||||||
const maxSize = data.maxSizeBytes;
|
const maxSize = data.maxSizeBytes;
|
||||||
const subtitle = maxSize
|
const subtitle = maxSize
|
||||||
@@ -263,15 +212,14 @@ export default class ImagesCacheOverview extends PureComponent<
|
|||||||
onImageSelected={this.onImageSelected}
|
onImageSelected={this.onImageSelected}
|
||||||
selectedImage={this.state.selectedImage}
|
selectedImage={this.state.selectedImage}
|
||||||
imagesMap={this.props.imagesMap}
|
imagesMap={this.props.imagesMap}
|
||||||
size={this.state.size}
|
|
||||||
events={this.props.events}
|
events={this.props.events}
|
||||||
onClear={onClear}
|
onClear={onClear}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ImagesCacheOverview.Content>
|
</Layout.ScrollContainer>
|
||||||
)}
|
)}
|
||||||
</ImagesCacheOverview.Container>
|
</Layout.Top>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,7 +232,6 @@ class ImageGrid extends PureComponent<{
|
|||||||
onImageSelected: (image: ImageId) => void;
|
onImageSelected: (image: ImageId) => void;
|
||||||
onClear: (() => void) | undefined;
|
onClear: (() => void) | undefined;
|
||||||
imagesMap: ImagesMap;
|
imagesMap: ImagesMap;
|
||||||
size: number;
|
|
||||||
events: Array<ImageEventWithId>;
|
events: Array<ImageEventWithId>;
|
||||||
}> {
|
}> {
|
||||||
static Content = styled.div({
|
static Content = styled.div({
|
||||||
@@ -298,31 +245,48 @@ class ImageGrid extends PureComponent<{
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROW_SIZE = 6;
|
||||||
|
const imageRows = Array(Math.ceil(images.length / ROW_SIZE))
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => index * ROW_SIZE)
|
||||||
|
.map((begin) => images.slice(begin, begin + ROW_SIZE));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Layout.Container gap>
|
||||||
<ImageGridHeader
|
<ImageGridHeader
|
||||||
key="header"
|
key="header"
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
subtitle={this.props.subtitle}
|
subtitle={this.props.subtitle}
|
||||||
onClear={this.props.onClear}
|
onClear={this.props.onClear}
|
||||||
/>
|
/>
|
||||||
<ImageGrid.Content key="content">
|
|
||||||
{images.map((imageId) => (
|
<Layout.Container pad>
|
||||||
|
{imageRows.map((row, rowIndex) => (
|
||||||
|
<Layout.Container pad key={rowIndex}>
|
||||||
|
<Row key={rowIndex} align={'middle'} gutter={[8, 24]}>
|
||||||
|
{row.map((imageId, colIndex) => (
|
||||||
|
<Col key={colIndex} span={24 / ROW_SIZE}>
|
||||||
<ImageItem
|
<ImageItem
|
||||||
imageId={imageId}
|
imageId={imageId}
|
||||||
image={this.props.imagesMap[imageId]}
|
image={this.props.imagesMap[imageId]}
|
||||||
key={imageId}
|
key={imageId}
|
||||||
selected={selectedImage != null && selectedImage === imageId}
|
selected={
|
||||||
|
selectedImage != null && selectedImage === imageId
|
||||||
|
}
|
||||||
onSelected={onImageSelected}
|
onSelected={onImageSelected}
|
||||||
size={this.props.size}
|
|
||||||
numberOfRequests={
|
numberOfRequests={
|
||||||
this.props.events.filter((e) => e.imageIds.includes(imageId))
|
this.props.events.filter((e) =>
|
||||||
.length
|
e.imageIds.includes(imageId),
|
||||||
|
).length
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
</Col>
|
||||||
))}
|
))}
|
||||||
</ImageGrid.Content>
|
</Row>
|
||||||
</>
|
</Layout.Container>
|
||||||
|
))}
|
||||||
|
</Layout.Container>
|
||||||
|
</Layout.Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,56 +296,23 @@ class ImageGridHeader extends PureComponent<{
|
|||||||
subtitle: string;
|
subtitle: string;
|
||||||
onClear: (() => void) | undefined;
|
onClear: (() => void) | undefined;
|
||||||
}> {
|
}> {
|
||||||
static Container = styled(FlexRow)({
|
|
||||||
color: colors.dark70,
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
marginLeft: 15,
|
|
||||||
marginRight: 15,
|
|
||||||
marginBottom: 15,
|
|
||||||
borderBottom: `1px solid ${colors.light10}`,
|
|
||||||
flexShrink: 0,
|
|
||||||
alignItems: 'center',
|
|
||||||
position: 'sticky',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
backgroundColor: colors.white,
|
|
||||||
zIndex: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
static Heading = styled.span({
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: 600,
|
|
||||||
});
|
|
||||||
|
|
||||||
static Subtitle = styled.span({
|
static Subtitle = styled.span({
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
marginLeft: 15,
|
|
||||||
});
|
|
||||||
|
|
||||||
static ClearButton = styled(Button)({
|
|
||||||
alignSelf: 'center',
|
|
||||||
height: 30,
|
|
||||||
marginLeft: 'auto',
|
|
||||||
width: 100,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ImageGridHeader.Container>
|
<Layout.Horizontal gap pad grow borderBottom>
|
||||||
<ImageGridHeader.Heading>{this.props.title}</ImageGridHeader.Heading>
|
<Typography.Title>{this.props.title}</Typography.Title>
|
||||||
<ImageGridHeader.Subtitle>
|
<ImageGridHeader.Subtitle>
|
||||||
{this.props.subtitle}
|
{this.props.subtitle}
|
||||||
</ImageGridHeader.Subtitle>
|
</ImageGridHeader.Subtitle>
|
||||||
<Spacer />
|
|
||||||
{this.props.onClear ? (
|
{this.props.onClear ? (
|
||||||
<ImageGridHeader.ClearButton onClick={this.props.onClear}>
|
<Button onClick={this.props.onClear}>Clear Cache</Button>
|
||||||
Clear Cache
|
|
||||||
</ImageGridHeader.ClearButton>
|
|
||||||
) : null}
|
) : null}
|
||||||
</ImageGridHeader.Container>
|
</Layout.Horizontal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,36 +325,16 @@ class ImageItem extends PureComponent<{
|
|||||||
size: number;
|
size: number;
|
||||||
numberOfRequests: number;
|
numberOfRequests: number;
|
||||||
}> {
|
}> {
|
||||||
static Container = styled(FlexBox)<{size: number}>((props) => ({
|
static defaultProps = {
|
||||||
float: 'left',
|
size: 150,
|
||||||
alignItems: 'center',
|
};
|
||||||
justifyContent: 'center',
|
|
||||||
flexShrink: 0,
|
|
||||||
height: props.size,
|
|
||||||
width: props.size,
|
|
||||||
borderRadius: 4,
|
|
||||||
marginRight: 15,
|
|
||||||
marginBottom: 15,
|
|
||||||
backgroundColor: colors.light02,
|
|
||||||
}));
|
|
||||||
|
|
||||||
static Image = styled.img({
|
|
||||||
borderRadius: 4,
|
|
||||||
maxHeight: '100%',
|
|
||||||
maxWidth: '100%',
|
|
||||||
objectFit: 'contain',
|
|
||||||
});
|
|
||||||
|
|
||||||
static Loading = styled.span({
|
|
||||||
padding: '0 0',
|
|
||||||
});
|
|
||||||
|
|
||||||
static SelectedHighlight = styled.div<{selected: boolean}>((props) => ({
|
static SelectedHighlight = styled.div<{selected: boolean}>((props) => ({
|
||||||
borderColor: colors.highlight,
|
borderColor: theme.primaryColor,
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderWidth: props.selected ? 3 : 0,
|
borderWidth: props.selected ? 3 : 0,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: props.selected ? `inset 0 0 0 1px ${colors.white}` : 'none',
|
boxShadow: props.selected ? `inset 0 0 0 1px ${theme.white}` : 'none',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -431,10 +342,12 @@ class ImageItem extends PureComponent<{
|
|||||||
top: 0,
|
top: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
static HoverOverlay = styled(FlexColumn)<{selected: boolean; size: number}>(
|
static HoverOverlay = styled(Layout.Container)<{
|
||||||
(props) => ({
|
selected: boolean;
|
||||||
|
size: number;
|
||||||
|
}>((props) => ({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.whiteAlpha80,
|
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||||
bottom: props.selected ? 4 : 0,
|
bottom: props.selected ? 4 : 0,
|
||||||
fontSize: props.size > 100 ? 16 : 11,
|
fontSize: props.size > 100 ? 16 : 11,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -448,8 +361,7 @@ class ImageItem extends PureComponent<{
|
|||||||
'&:hover': {
|
'&:hover': {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
static MemoryLabel = styled.span({
|
static MemoryLabel = styled.span({
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -460,25 +372,13 @@ class ImageItem extends PureComponent<{
|
|||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
});
|
});
|
||||||
|
|
||||||
static Events = styled.div({
|
static EventBadge = styled(Badge)({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: -5,
|
top: 0,
|
||||||
right: -5,
|
right: 0,
|
||||||
color: colors.white,
|
zIndex: 1,
|
||||||
backgroundColor: colors.highlight,
|
|
||||||
fontWeight: 600,
|
|
||||||
borderRadius: 10,
|
|
||||||
fontSize: '0.85em',
|
|
||||||
zIndex: 2,
|
|
||||||
lineHeight: '20px',
|
|
||||||
width: 20,
|
|
||||||
textAlign: 'center',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
size: 150,
|
|
||||||
};
|
|
||||||
|
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
this.props.onSelected(this.props.imageId);
|
this.props.onSelected(this.props.imageId);
|
||||||
};
|
};
|
||||||
@@ -487,14 +387,14 @@ class ImageItem extends PureComponent<{
|
|||||||
const {image, selected, size, numberOfRequests} = this.props;
|
const {image, selected, size, numberOfRequests} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageItem.Container onClick={this.onClick} size={size}>
|
<Layout.Container onClick={this.onClick} gap>
|
||||||
{numberOfRequests > 0 && image != null && (
|
{numberOfRequests > 0 && image != null && (
|
||||||
<ImageItem.Events>{numberOfRequests}</ImageItem.Events>
|
<ImageItem.EventBadge count={numberOfRequests}></ImageItem.EventBadge>
|
||||||
)}
|
)}
|
||||||
{image != null ? (
|
{image != null ? (
|
||||||
<ImageItem.Image src={image.data} />
|
<Image src={image.data} preview={false} />
|
||||||
) : (
|
) : (
|
||||||
<LoadingIndicator size={25} />
|
<Skeleton.Image />
|
||||||
)}
|
)}
|
||||||
<ImageItem.SelectedHighlight selected={selected} />
|
<ImageItem.SelectedHighlight selected={selected} />
|
||||||
{image != null && (
|
{image != null && (
|
||||||
@@ -507,7 +407,7 @@ class ImageItem extends PureComponent<{
|
|||||||
</ImageItem.SizeLabel>
|
</ImageItem.SizeLabel>
|
||||||
</ImageItem.HoverOverlay>
|
</ImageItem.HoverOverlay>
|
||||||
)}
|
)}
|
||||||
</ImageItem.Container>
|
</Layout.Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,20 +9,15 @@
|
|||||||
|
|
||||||
import {ImageData} from './api';
|
import {ImageData} from './api';
|
||||||
import {ImageEventWithId} from './index';
|
import {ImageEventWithId} from './index';
|
||||||
|
import React, {Component} from 'react';
|
||||||
import {
|
import {
|
||||||
Component,
|
Layout,
|
||||||
ContextMenu,
|
theme,
|
||||||
DataDescription,
|
|
||||||
Text,
|
|
||||||
Panel,
|
|
||||||
ManagedDataInspector,
|
|
||||||
FlexColumn,
|
|
||||||
FlexRow,
|
|
||||||
colors,
|
|
||||||
styled,
|
styled,
|
||||||
} from 'flipper';
|
DataDescription,
|
||||||
import React from 'react';
|
Panel,
|
||||||
import {clipboard, MenuItemConstructorOptions} from 'electron';
|
DataInspector,
|
||||||
|
} from 'flipper-plugin';
|
||||||
|
|
||||||
type ImagesSidebarProps = {
|
type ImagesSidebarProps = {
|
||||||
image: ImageData;
|
image: ImageData;
|
||||||
@@ -32,10 +27,10 @@ type ImagesSidebarProps = {
|
|||||||
type ImagesSidebarState = {};
|
type ImagesSidebarState = {};
|
||||||
|
|
||||||
const DataDescriptionKey = styled.span({
|
const DataDescriptionKey = styled.span({
|
||||||
color: colors.grapeDark1,
|
color: theme.textColorPrimary,
|
||||||
});
|
});
|
||||||
|
|
||||||
const WordBreakFlexColumn = styled(FlexColumn)({
|
const WordBreakFlexColumn = styled(Layout.Container)({
|
||||||
wordBreak: 'break-all',
|
wordBreak: 'break-all',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,37 +57,22 @@ export default class ImagesSidebar extends Component<
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMenuItems: MenuItemConstructorOptions[] = [
|
|
||||||
{
|
|
||||||
label: 'Copy URI',
|
|
||||||
click: () => clipboard.writeText(this.props.image.uri!),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel heading="Sources" floating={false}>
|
<Panel title="Sources" pad>
|
||||||
<FlexRow>
|
|
||||||
<FlexColumn>
|
|
||||||
<DataDescriptionKey>URI</DataDescriptionKey>
|
|
||||||
</FlexColumn>
|
|
||||||
<FlexColumn>
|
|
||||||
<span key="sep">: </span>
|
|
||||||
</FlexColumn>
|
|
||||||
<WordBreakFlexColumn>
|
<WordBreakFlexColumn>
|
||||||
<ContextMenu component="span" items={contextMenuItems}>
|
<span>
|
||||||
|
URI<span key="sep">: </span>
|
||||||
<DataDescription
|
<DataDescription
|
||||||
type="string"
|
type="string"
|
||||||
value={this.props.image.uri}
|
value={this.props.image.uri}
|
||||||
setValue={null}
|
setValue={null}
|
||||||
/>
|
/>
|
||||||
</ContextMenu>
|
</span>
|
||||||
</WordBreakFlexColumn>
|
</WordBreakFlexColumn>
|
||||||
</FlexRow>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EventDetails extends Component<{
|
class EventDetails extends Component<{
|
||||||
event: ImageEventWithId;
|
event: ImageEventWithId;
|
||||||
}> {
|
}> {
|
||||||
@@ -100,11 +80,11 @@ class EventDetails extends Component<{
|
|||||||
const {event} = this.props;
|
const {event} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel heading={<RequestHeader event={event} />} floating={false} padded>
|
<Panel title={requestHeader(event)} pad>
|
||||||
<p>
|
<p>
|
||||||
<DataDescriptionKey>Attribution</DataDescriptionKey>
|
<DataDescriptionKey>Attribution</DataDescriptionKey>
|
||||||
<span key="sep">: </span>
|
<span key="sep">: </span>
|
||||||
<ManagedDataInspector data={event.attribution} />
|
<DataInspector data={event.attribution} />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<DataDescriptionKey>Time start</DataDescriptionKey>
|
<DataDescriptionKey>Time start</DataDescriptionKey>
|
||||||
@@ -163,24 +143,11 @@ class EventDetails extends Component<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestHeader extends Component<{
|
function requestHeader(event: ImageEventWithId) {
|
||||||
event: ImageEventWithId;
|
const date = new Date(event.startTime);
|
||||||
}> {
|
const dateString = `${date.toTimeString().split(' ')[0]}.${(
|
||||||
dateString = (timestamp: number) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
return `${date.toTimeString().split(' ')[0]}.${(
|
|
||||||
'000' + date.getMilliseconds()
|
'000' + date.getMilliseconds()
|
||||||
).substr(-3)}`;
|
).substr(-3)}`;
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
return (event.viewport ? 'Request' : 'Prefetch') + ' at ' + dateString;
|
||||||
const {event} = this.props;
|
|
||||||
const durationMs = event.endTime - event.startTime;
|
|
||||||
return (
|
|
||||||
<Text>
|
|
||||||
{event.viewport ? 'Request' : 'Prefetch'} at{' '}
|
|
||||||
{this.dateString(event.startTime)} ({durationMs}ms)
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,105 +7,47 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Block, Button, colors, FlexColumn, styled, Glyph} from 'flipper';
|
import React, {Component} from 'react';
|
||||||
import React, {ChangeEvent, Component} from 'react';
|
import {Layout} from 'flipper-plugin';
|
||||||
|
import {Button, Menu, Checkbox, Dropdown} from 'antd';
|
||||||
|
import {DownOutlined} from '@ant-design/icons';
|
||||||
|
import type {CheckboxChangeEvent} from 'antd/lib/checkbox';
|
||||||
|
|
||||||
const Container = styled(Block)({
|
export default class MultipleSelect extends Component<{
|
||||||
position: 'relative',
|
|
||||||
marginLeft: '10px',
|
|
||||||
});
|
|
||||||
|
|
||||||
const List = styled(FlexColumn)<{visibleList: boolean}>((props) => ({
|
|
||||||
display: props.visibleList ? 'flex' : 'none',
|
|
||||||
position: 'absolute',
|
|
||||||
top: '32px',
|
|
||||||
left: 0,
|
|
||||||
zIndex: 4,
|
|
||||||
width: 'auto',
|
|
||||||
minWidth: '200px',
|
|
||||||
backgroundColor: colors.white,
|
|
||||||
borderWidth: '1px',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderColor: colors.macOSTitleBarButtonBorderBottom,
|
|
||||||
borderRadius: 4,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ListItem = styled.label({
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%',
|
|
||||||
color: colors.light50,
|
|
||||||
fontSize: '11px',
|
|
||||||
padding: '0 5px',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: colors.macOSTitleBarButtonBackgroundActiveHighlight,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const Checkbox = styled.input({
|
|
||||||
display: 'inline-block',
|
|
||||||
marginRight: 5,
|
|
||||||
verticalAlign: 'middle',
|
|
||||||
});
|
|
||||||
|
|
||||||
const StyledGlyph = styled(Glyph)({
|
|
||||||
marginLeft: '4px',
|
|
||||||
});
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
visibleList: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class MultipleSelect extends Component<
|
|
||||||
{
|
|
||||||
selected: Set<string>;
|
selected: Set<string>;
|
||||||
|
|
||||||
options: Set<string>;
|
options: Set<string>;
|
||||||
|
|
||||||
onChange: (selectedItem: string, checked: boolean) => void;
|
|
||||||
|
|
||||||
label: string;
|
label: string;
|
||||||
},
|
onChange: (selectedItem: string, checked: boolean) => void;
|
||||||
State
|
}> {
|
||||||
> {
|
handleOnChange = (option: string, event: CheckboxChangeEvent) => {
|
||||||
state = {
|
this.props.onChange(option, event.target.checked);
|
||||||
visibleList: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOnChange = (event: ChangeEvent<HTMLInputElement>) => {
|
menu = () => {
|
||||||
const {
|
return (
|
||||||
target: {value, checked},
|
<Menu>
|
||||||
} = event;
|
{Array.from(this.props.options).map((option, index) => (
|
||||||
this.props.onChange(value, checked);
|
<Menu.Item key={index} disabled>
|
||||||
|
<Checkbox
|
||||||
|
onChange={(e) => this.handleOnChange(option, e)}
|
||||||
|
checked={this.props.selected.has(option)}
|
||||||
|
/>{' '}
|
||||||
|
{option}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleList = () => this.setState({visibleList: !this.state.visibleList});
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {selected, label, options} = this.props;
|
|
||||||
const {visibleList} = this.state;
|
|
||||||
const icon = visibleList ? 'chevron-up' : 'chevron-down';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Layout.Container>
|
||||||
<Button onClick={this.toggleList}>
|
<Dropdown overlay={this.menu}>
|
||||||
{label} <StyledGlyph name={icon} />
|
<Button>
|
||||||
|
Surfaces <DownOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<List visibleList={visibleList}>
|
</Dropdown>
|
||||||
{Array.from(options).map((option, index) => (
|
</Layout.Container>
|
||||||
<ListItem key={index}>
|
|
||||||
<Checkbox
|
|
||||||
onChange={this.handleOnChange}
|
|
||||||
checked={selected.has(option)}
|
|
||||||
value={option}
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
{option}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,21 @@ import {
|
|||||||
AndroidCloseableReferenceLeakEvent,
|
AndroidCloseableReferenceLeakEvent,
|
||||||
CacheInfo,
|
CacheInfo,
|
||||||
} from './api';
|
} from './api';
|
||||||
import {Fragment} from 'react';
|
|
||||||
import {ImagesMap} from './ImagePool';
|
import {ImagesMap} from './ImagePool';
|
||||||
import {PluginClient, createState, usePlugin, useValue} from 'flipper-plugin';
|
import {
|
||||||
|
PluginClient,
|
||||||
|
createState,
|
||||||
|
usePlugin,
|
||||||
|
useValue,
|
||||||
|
DetailSidebar,
|
||||||
|
Layout,
|
||||||
|
} from 'flipper-plugin';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImagesCacheOverview from './ImagesCacheOverview';
|
import ImagesCacheOverview from './ImagesCacheOverview';
|
||||||
import {
|
import {isProduction} from 'flipper';
|
||||||
FlexRow,
|
|
||||||
Text,
|
import {Typography} from 'antd';
|
||||||
DetailSidebar,
|
|
||||||
colors,
|
|
||||||
styled,
|
|
||||||
isProduction,
|
|
||||||
} from 'flipper';
|
|
||||||
import ImagesSidebar from './ImagesSidebar';
|
import ImagesSidebar from './ImagesSidebar';
|
||||||
import ImagePool from './ImagePool';
|
import ImagePool from './ImagePool';
|
||||||
|
|
||||||
@@ -38,18 +40,6 @@ export type AllImageEventsInfo = {
|
|||||||
events: Array<ImageEventWithId>;
|
events: Array<ImageEventWithId>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptySidebar = styled(FlexRow)({
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
color: colors.light30,
|
|
||||||
padding: 15,
|
|
||||||
fontSize: 16,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const InlineFlexRow = styled(FlexRow)({
|
|
||||||
display: 'inline-block',
|
|
||||||
});
|
|
||||||
|
|
||||||
const surfaceDefaultText = 'SELECT ALL SURFACES';
|
const surfaceDefaultText = 'SELECT ALL SURFACES';
|
||||||
|
|
||||||
const debugLog = (...args: any[]) => {
|
const debugLog = (...args: any[]) => {
|
||||||
@@ -113,18 +103,17 @@ export function plugin(client: PluginClient<Events, Methods>) {
|
|||||||
id: event.identityHashCode,
|
id: event.identityHashCode,
|
||||||
title: `Leaked CloseableReference: ${event.className}`,
|
title: `Leaked CloseableReference: ${event.className}`,
|
||||||
message: (
|
message: (
|
||||||
<Fragment>
|
<Layout.Container>
|
||||||
<InlineFlexRow>
|
<Typography.Text>CloseableReference leaked for </Typography.Text>
|
||||||
CloseableReference leaked for <Text code>{event.className}</Text>
|
<Typography.Text code>{event.className}</Typography.Text>
|
||||||
|
<Typography.Text>
|
||||||
(identity hashcode: {event.identityHashCode}).
|
(identity hashcode: {event.identityHashCode}).
|
||||||
</InlineFlexRow>
|
</Typography.Text>
|
||||||
<InlineFlexRow>
|
<Typography.Text strong>Stacktrace:</Typography.Text>
|
||||||
<Text bold>Stacktrace:</Text>
|
<Typography.Text code>
|
||||||
</InlineFlexRow>
|
{event.stacktrace || '<unavailable>'}
|
||||||
<InlineFlexRow>
|
</Typography.Text>
|
||||||
<Text code>{event.stacktrace || '<unavailable>'}</Text>
|
</Layout.Container>
|
||||||
</InlineFlexRow>
|
|
||||||
</Fragment>
|
|
||||||
),
|
),
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
category: 'closeablereference_leak',
|
category: 'closeablereference_leak',
|
||||||
@@ -465,11 +454,11 @@ function Sidebar() {
|
|||||||
|
|
||||||
if (currentSelectedImage == null) {
|
if (currentSelectedImage == null) {
|
||||||
return (
|
return (
|
||||||
<EmptySidebar grow>
|
<Layout.Container pad>
|
||||||
<Text align="center">
|
<Typography.Text>
|
||||||
Select an image to see the events associated with it.
|
Select an image to see the events associated with it.
|
||||||
</Text>
|
</Typography.Text>
|
||||||
</EmptySidebar>
|
</Layout.Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user