Allow multiple surface filter selection

Summary: Added the ability to select multiple surface filters.

Reviewed By: jknoxville

Differential Revision: D18139865

fbshipit-source-id: d1b98d1f13febbbf20e8fed6df3a34909a27be89
This commit is contained in:
Udoka Nkwocha
2019-10-28 06:03:46 -07:00
committed by Facebook Github Bot
parent 8cb56a7740
commit b073c90e24
3 changed files with 186 additions and 25 deletions

View File

@@ -24,6 +24,7 @@ import {
ToggleButton, ToggleButton,
Text, Text,
} from 'flipper'; } from 'flipper';
import MultipleSelect from './MultipleSelect.js';
import type {ImagesMap} from './ImagePool.js'; import type {ImagesMap} from './ImagePool.js';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
import {PureComponent} from 'react'; import {PureComponent} from 'react';
@@ -69,9 +70,10 @@ function Toggle(props: ToggleProps) {
type ImagesCacheOverviewProps = { type ImagesCacheOverviewProps = {
onColdStartChange: (checked: boolean) => void, onColdStartChange: (checked: boolean) => void,
coldStartFilter: boolean, coldStartFilter: boolean,
surfaceOptions: {[key: string]: string}, allSurfacesOption: string,
selectedSurface: string, surfaceOptions: Set<string>,
onChangeSurface: (key: string) => void, selectedSurfaces: Set<string>,
onChangeSurface: (key: Set<string>) => void,
images: ImagesList, images: ImagesList,
onClear: (type: string) => void, onClear: (type: string) => void,
onTrimMemory: () => void, onTrimMemory: () => void,
@@ -152,12 +154,47 @@ export default class ImagesCacheOverview extends PureComponent<
onChangeSize = (e: SyntheticInputEvent<HTMLInputElement>) => onChangeSize = (e: SyntheticInputEvent<HTMLInputElement>) =>
this.setState({size: parseInt(e.target.value, 10)}); this.setState({size: parseInt(e.target.value, 10)});
onSurfaceOptionsChange = (selectedItem: string, checked: boolean) => {
const {allSurfacesOption, surfaceOptions} = this.props;
const selectedSurfaces = new Set([...this.props.selectedSurfaces]);
if (checked && selectedItem === allSurfacesOption) {
this.props.onChangeSurface(surfaceOptions);
return;
}
if (!checked && selectedSurfaces.size === 1) {
return;
}
if (selectedItem !== allSurfacesOption) {
selectedSurfaces.delete(allSurfacesOption);
if (checked) {
selectedSurfaces.add(selectedItem);
} else {
selectedSurfaces.delete(selectedItem);
}
}
if (
surfaceOptions.size - selectedSurfaces.size === 1 &&
!selectedSurfaces.has(allSurfacesOption)
) {
selectedSurfaces.add(allSurfacesOption);
}
this.props.onChangeSurface(selectedSurfaces);
};
render() { render() {
const hasImages = const hasImages =
this.props.images.reduce( this.props.images.reduce(
(c, cacheInfo) => c + cacheInfo.imageIds.length, (c, cacheInfo) => c + cacheInfo.imageIds.length,
0, 0,
) > 0; ) > 0;
return ( return (
<ImagesCacheOverview.Container <ImagesCacheOverview.Container
grow={true} grow={true}
@@ -168,10 +205,11 @@ export default class ImagesCacheOverview extends PureComponent<
Trim Memory Trim Memory
</Button> </Button>
<Button onClick={this.props.onRefresh}>Refresh</Button> <Button onClick={this.props.onRefresh}>Refresh</Button>
<StyledSelect <MultipleSelect
selected={this.props.selectedSurfaces}
options={this.props.surfaceOptions} options={this.props.surfaceOptions}
selected={this.props.selectedSurface} onChange={this.onSurfaceOptionsChange}
onChange={this.props.onChangeSurface} label="Surfaces"
/> />
<Toggle <Toggle
onClick={this.onEnableAutoRefreshToggled} onClick={this.onEnableAutoRefreshToggled}
@@ -208,7 +246,7 @@ export default class ImagesCacheOverview extends PureComponent<
</ImagesCacheOverview.Empty> </ImagesCacheOverview.Empty>
) : ( ) : (
<ImagesCacheOverview.Content> <ImagesCacheOverview.Content>
{this.props.images.map(data => { {this.props.images.map((data, index) => {
const maxSize = data.maxSizeBytes; const maxSize = data.maxSizeBytes;
const subtitle = maxSize const subtitle = maxSize
? formatMB(data.sizeBytes) + ' / ' + formatMB(maxSize) ? formatMB(data.sizeBytes) + ' / ' + formatMB(maxSize)
@@ -218,6 +256,7 @@ export default class ImagesCacheOverview extends PureComponent<
: null; : null;
return ( return (
<ImageGrid <ImageGrid
key={index}
title={data.cacheType} title={data.cacheType}
subtitle={subtitle} subtitle={subtitle}
images={data.imageIds} images={data.imageIds}

View File

@@ -0,0 +1,111 @@
/**
* 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 {Block, Button, colors, FlexColumn, styled, Glyph} from 'flipper';
import React, {Component} from 'react';
const Container = styled(Block)({
position: 'relative',
marginLeft: '10px',
});
const List = styled(FlexColumn)((props: {visibleList: boolean}) => ({
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>,
options: Set<string>,
onChange: (selectedItem: string, checked: boolean) => void,
label: string,
},
State,
> {
state = {
visibleList: false,
};
handleOnChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
const {
target: {value, checked},
} = event;
this.props.onChange(value, checked);
};
toggleList = () => this.setState({visibleList: !this.state.visibleList});
render() {
const {selected, label, options} = this.props;
const {visibleList} = this.state;
const icon = visibleList ? 'chevron-up' : 'chevron-down';
return (
<Container>
<Button onClick={this.toggleList}>
{label} <StyledGlyph name={icon} />
</Button>
<List visibleList={visibleList}>
{Array.from(options).map((option, index) => (
<ListItem key={index}>
<Checkbox
onChange={this.handleOnChange}
checked={selected.has(option)}
value={option}
type="checkbox"
/>
{option}
</ListItem>
))}
</List>
</Container>
);
}
}

View File

@@ -48,7 +48,7 @@ export type PersistedState = {
}; };
type PluginState = { type PluginState = {
selectedSurface: string, selectedSurfaces: Set<string>,
selectedImage: ?ImageId, selectedImage: ?ImageId,
isDebugOverlayEnabled: boolean, isDebugOverlayEnabled: boolean,
isAutoRefreshEnabled: boolean, isAutoRefreshEnabled: boolean,
@@ -272,7 +272,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
nextEventId: number = 1; nextEventId: number = 1;
state = { state = {
selectedSurface: surfaceDefaultText, selectedSurfaces: new Set([surfaceDefaultText]),
selectedImage: null, selectedImage: null,
isDebugOverlayEnabled: false, isDebugOverlayEnabled: false,
isAutoRefreshEnabled: false, isAutoRefreshEnabled: false,
@@ -283,12 +283,13 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
filterImages = ( filterImages = (
images: ImagesList, images: ImagesList,
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>,
surface: string, surfaces: Set<string>,
coldStart: boolean, coldStart: boolean,
): ImagesList => { ): ImagesList => {
if (!surface || (surface === surfaceDefaultText && !coldStart)) { if (!surfaces || (surfaces.has(surfaceDefaultText) && !coldStart)) {
return images; return images;
} }
const imageList = images.map((image: CacheInfo) => { const imageList = images.map((image: CacheInfo) => {
const imageIdList = image.imageIds.filter(imageID => { const imageIdList = image.imageIds.filter(imageID => {
const filteredEvents = events.filter((event: ImageEventWithId) => { const filteredEvents = events.filter((event: ImageEventWithId) => {
@@ -298,13 +299,14 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
event.imageIds && event.imageIds &&
event.imageIds.includes(imageID); event.imageIds.includes(imageID);
if (surface === surfaceDefaultText) { if (surfaces.has(surfaceDefaultText)) {
return output && coldStart && event.coldStart; return output && coldStart && event.coldStart;
} }
return ( return (
(!coldStart || (coldStart && event.coldStart)) && (!coldStart || (coldStart && event.coldStart)) &&
output && output &&
event.attribution[0] == surface surfaces.has(event.attribution[0])
); );
}); });
return filteredEvents.length > 0; return filteredEvents.length > 0;
@@ -330,9 +332,10 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
const images = this.filterImages( const images = this.filterImages(
this.props.persistedState.images, this.props.persistedState.images,
this.props.persistedState.events, this.props.persistedState.events,
this.state.selectedSurface, this.state.selectedSurfaces,
this.state.coldStartFilter, this.state.coldStartFilter,
); );
this.setState({images}); this.setState({images});
} }
@@ -342,17 +345,18 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
updateImagesOnUI = ( updateImagesOnUI = (
images: ImagesList, images: ImagesList,
surface: string, surfaces: Set<string>,
coldStart: boolean, coldStart: boolean,
) => { ) => {
const filteredImages = this.filterImages( const filteredImages = this.filterImages(
images, images,
this.props.persistedState.events, this.props.persistedState.events,
surface, surfaces,
coldStart, coldStart,
); );
this.setState({ this.setState({
selectedSurface: surface, selectedSurfaces: surfaces,
images: filteredImages, images: filteredImages,
coldStartFilter: coldStart, coldStartFilter: coldStart,
}); });
@@ -366,7 +370,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
this.props.setPersistedState({images: response.levels}); this.props.setPersistedState({images: response.levels});
this.updateImagesOnUI( this.updateImagesOnUI(
this.props.persistedState.images, this.props.persistedState.images,
this.state.selectedSurface, this.state.selectedSurfaces,
this.state.coldStartFilter, this.state.coldStartFilter,
); );
}); });
@@ -431,10 +435,10 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
return <ImagesSidebar image={maybeImage} events={events} />; return <ImagesSidebar image={maybeImage} events={events} />;
}; };
onSurfaceChange = (surface: string) => { onSurfaceChange = (surfaces: Set<string>) => {
this.updateImagesOnUI( this.updateImagesOnUI(
this.props.persistedState.images, this.props.persistedState.images,
surface, surfaces,
this.state.coldStartFilter, this.state.coldStartFilter,
); );
}; };
@@ -442,7 +446,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
onColdStartChange = (checked: boolean) => { onColdStartChange = (checked: boolean) => {
this.updateImagesOnUI( this.updateImagesOnUI(
this.props.persistedState.images, this.props.persistedState.images,
this.state.selectedSurface, this.state.selectedSurfaces,
checked, checked,
); );
}; };
@@ -457,15 +461,22 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
render() { render() {
const options = [...this.props.persistedState.surfaceList].reduce( const options = [...this.props.persistedState.surfaceList].reduce(
(acc, item) => { (acc, item) => {
return {...acc, [item]: item}; return [...acc, item];
}, },
{[surfaceDefaultText]: surfaceDefaultText}, [surfaceDefaultText],
); );
let {selectedSurfaces} = this.state;
if (selectedSurfaces.has(surfaceDefaultText)) {
selectedSurfaces = new Set(options);
}
return ( return (
<React.Fragment> <React.Fragment>
<ImagesCacheOverview <ImagesCacheOverview
surfaceOptions={options} allSurfacesOption={surfaceDefaultText}
selectedSurface={this.state.selectedSurface} surfaceOptions={new Set(options)}
selectedSurfaces={selectedSurfaces}
onChangeSurface={this.onSurfaceChange} onChangeSurface={this.onSurfaceChange}
coldStartFilter={this.state.coldStartFilter} coldStartFilter={this.state.coldStartFilter}
onColdStartChange={this.onColdStartChange} onColdStartChange={this.onColdStartChange}