Convert Flipper plugin "Fresco" to TypeScript

Summary:
Moves the Fresco plugin to TypeScript, including fixing any typing/nullable warnings following this.

Note that parameters of event handlers previously using the SyntheticInputEvent flow type now accept a parameter of type `any`. This is done since the InputEvent api is not covered by ts bindings, as the interface is deemed experimental and not fully covered by browsers (see https://fettblog.eu/typescript-react/events/#wheres-inputevent for more info)

Reviewed By: passy

Differential Revision: D18201893

fbshipit-source-id: 41d1e5fc1ceaa8f8453c0f5929e754b7c32c0eb8
This commit is contained in:
Gijs Weterings
2019-10-30 08:28:23 -07:00
committed by Facebook Github Bot
parent 7ca230a9c6
commit d0ab63297f
10 changed files with 279 additions and 267 deletions

View File

@@ -7,15 +7,15 @@
* @format * @format
*/ */
import type {ImageId, ImageData} from './api.js'; import {ImageId, ImageData} from './api.js';
export type ImagesMap = {[imageId: ImageId]: ImageData}; export type ImagesMap = {[imageId in ImageId]: ImageData};
const maxInflightRequests = 10; const maxInflightRequests = 10;
export default class ImagePool { export default class ImagePool {
cache: ImagesMap = {}; cache: ImagesMap = {};
requested: {[imageId: ImageId]: boolean} = {}; requested: {[imageId in ImageId]: boolean} = {};
queued: Array<ImageId> = []; queued: Array<ImageId> = [];
inFlightRequests: number = 0; inFlightRequests: number = 0;
fetchImage: (imageId: ImageId) => void; fetchImage: (imageId: ImageId) => void;
@@ -59,7 +59,8 @@ export default class ImagePool {
delete this.requested[image.imageId]; delete this.requested[image.imageId];
if (this.queued.length > 0) { if (this.queued.length > 0) {
this.fetchImage(this.queued.pop()); const popped = this.queued.pop() as string;
this.fetchImage(popped);
} else { } else {
this.inFlightRequests--; this.inFlightRequests--;
} }

View File

@@ -7,8 +7,8 @@
* @format * @format
*/ */
import type {ImageId, ImageData, ImagesList} from './api.js'; import {CacheInfo, ImageId, ImageData, ImagesList} from './api';
import type {ImageEventWithId} from './index.js'; import {ImageEventWithId} from './index';
import { import {
Toolbar, Toolbar,
@@ -24,10 +24,10 @@ import {
ToggleButton, ToggleButton,
Text, Text,
} from 'flipper'; } from 'flipper';
import MultipleSelect from './MultipleSelect.js'; import MultipleSelect from './MultipleSelect';
import type {ImagesMap} from './ImagePool.js'; import {ImagesMap} from './ImagePool';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
import {PureComponent} from 'react'; import React, {ChangeEvent, KeyboardEvent, PureComponent} from 'react';
function formatMB(bytes: number) { function formatMB(bytes: number) {
return Math.floor(bytes / (1024 * 1024)) + 'MB'; return Math.floor(bytes / (1024 * 1024)) + 'MB';
@@ -37,11 +37,11 @@ function formatKB(bytes: number) {
return Math.floor(bytes / 1024) + 'KB'; return Math.floor(bytes / 1024) + 'KB';
} }
type ToggleProps = {| type ToggleProps = {
label: string, label: string;
onClick?: (newValue: boolean) => void, onClick?: (newValue: boolean) => void;
toggled: boolean, toggled: boolean;
|}; };
const ToolbarToggleButton = styled(ToggleButton)(_props => ({ const ToolbarToggleButton = styled(ToggleButton)(_props => ({
alignSelf: 'center', alignSelf: 'center',
@@ -68,31 +68,31 @@ function Toggle(props: ToggleProps) {
} }
type ImagesCacheOverviewProps = { type ImagesCacheOverviewProps = {
onColdStartChange: (checked: boolean) => void, onColdStartChange: (checked: boolean) => void;
coldStartFilter: boolean, coldStartFilter: boolean;
allSurfacesOption: string, allSurfacesOption: string;
surfaceOptions: Set<string>, surfaceOptions: Set<string>;
selectedSurfaces: Set<string>, selectedSurfaces: Set<string>;
onChangeSurface: (key: Set<string>) => void, onChangeSurface: (key: Set<string>) => void;
images: ImagesList, images: ImagesList;
onClear: (type: string) => void, onClear: (type: string) => void;
onTrimMemory: () => void, onTrimMemory: () => void;
onRefresh: () => void, onRefresh: () => void;
onEnableDebugOverlay: (enabled: boolean) => void, onEnableDebugOverlay: (enabled: boolean) => void;
isDebugOverlayEnabled: boolean, isDebugOverlayEnabled: boolean;
onEnableAutoRefresh: (enabled: boolean) => void, onEnableAutoRefresh: (enabled: boolean) => void;
isAutoRefreshEnabled: boolean, isAutoRefreshEnabled: boolean;
onImageSelected: (selectedImage: ImageId) => void, onImageSelected: (selectedImage: ImageId) => void;
imagesMap: ImagesMap, imagesMap: ImagesMap;
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>;
onTrackLeaks: (enabled: boolean) => void, onTrackLeaks: (enabled: boolean) => void;
isLeakTrackingEnabled: boolean, isLeakTrackingEnabled: boolean;
}; };
type ImagesCacheOverviewState = {| type ImagesCacheOverviewState = {
selectedImage: ?ImageId, selectedImage: ImageId | null;
size: number, size: number;
|}; };
const StyledSelect = styled(Select)(props => ({ const StyledSelect = styled(Select)(props => ({
marginLeft: 6, marginLeft: 6,
@@ -103,8 +103,13 @@ const StyledSelect = styled(Select)(props => ({
export default class ImagesCacheOverview extends PureComponent< export default class ImagesCacheOverview extends PureComponent<
ImagesCacheOverviewProps, ImagesCacheOverviewProps,
ImagesCacheOverviewState, ImagesCacheOverviewState
> { > {
state = {
selectedImage: null,
size: 150,
};
static Container = styled(FlexColumn)({ static Container = styled(FlexColumn)({
backgroundColor: colors.white, backgroundColor: colors.white,
}); });
@@ -121,17 +126,12 @@ export default class ImagesCacheOverview extends PureComponent<
width: '100%', width: '100%',
}); });
state = {
selectedImage: undefined,
size: 150,
};
onImageSelected = (selectedImage: ImageId) => { onImageSelected = (selectedImage: ImageId) => {
this.setState({selectedImage}); this.setState({selectedImage});
this.props.onImageSelected(selectedImage); this.props.onImageSelected(selectedImage);
}; };
onKeyDown = (e: SyntheticKeyboardEvent<*>) => { onKeyDown = (e: KeyboardEvent) => {
const selectedImage = this.state.selectedImage; const selectedImage = this.state.selectedImage;
const imagesMap = this.props.imagesMap; const imagesMap = this.props.imagesMap;
@@ -151,7 +151,7 @@ export default class ImagesCacheOverview extends PureComponent<
this.props.onEnableAutoRefresh(!this.props.isAutoRefreshEnabled); this.props.onEnableAutoRefresh(!this.props.isAutoRefreshEnabled);
}; };
onChangeSize = (e: SyntheticInputEvent<HTMLInputElement>) => onChangeSize = (e: ChangeEvent<HTMLInputElement>) =>
this.setState({size: parseInt(e.target.value, 10)}); this.setState({size: parseInt(e.target.value, 10)});
onSurfaceOptionsChange = (selectedItem: string, checked: boolean) => { onSurfaceOptionsChange = (selectedItem: string, checked: boolean) => {
@@ -199,7 +199,7 @@ export default class ImagesCacheOverview extends PureComponent<
<ImagesCacheOverview.Container <ImagesCacheOverview.Container
grow={true} grow={true}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
tabIndex="0"> tabIndex={0}>
<Toolbar position="top"> <Toolbar position="top">
<Button icon="trash" onClick={this.props.onTrimMemory}> <Button icon="trash" onClick={this.props.onTrimMemory}>
Trim Memory Trim Memory
@@ -242,18 +242,19 @@ export default class ImagesCacheOverview extends PureComponent<
</Toolbar> </Toolbar>
{!hasImages ? ( {!hasImages ? (
<ImagesCacheOverview.Empty> <ImagesCacheOverview.Empty>
<LoadingIndicator /> <LoadingIndicator size={50} />
</ImagesCacheOverview.Empty> </ImagesCacheOverview.Empty>
) : ( ) : (
<ImagesCacheOverview.Content> <ImagesCacheOverview.Content>
{this.props.images.map((data, index) => { {this.props.images.map((data: CacheInfo, index: number) => {
const maxSize = data.maxSizeBytes; const maxSize = data.maxSizeBytes;
const subtitle = maxSize const subtitle = maxSize
? formatMB(data.sizeBytes) + ' / ' + formatMB(maxSize) ? formatMB(data.sizeBytes) + ' / ' + formatMB(maxSize)
: formatMB(data.sizeBytes); : formatMB(data.sizeBytes);
const onClear = data.clearKey const onClear =
? () => this.props.onClear(data.clearKey) data.clearKey !== undefined
: null; ? () => this.props.onClear(data.clearKey as string)
: undefined;
return ( return (
<ImageGrid <ImageGrid
key={index} key={index}
@@ -277,15 +278,15 @@ export default class ImagesCacheOverview extends PureComponent<
} }
class ImageGrid extends PureComponent<{ class ImageGrid extends PureComponent<{
title: string, title: string;
subtitle: string, subtitle: string;
images: Array<ImageId>, images: Array<ImageId>;
selectedImage: ?ImageId, selectedImage: ImageId | null;
onImageSelected: (image: ImageId) => void, onImageSelected: (image: ImageId) => void;
onClear: ?() => void, onClear: (() => void) | undefined;
imagesMap: ImagesMap, imagesMap: ImagesMap;
size: number, size: number;
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>;
}> { }> {
static Content = styled('div')({ static Content = styled('div')({
paddingLeft: 15, paddingLeft: 15,
@@ -325,9 +326,9 @@ class ImageGrid extends PureComponent<{
} }
class ImageGridHeader extends PureComponent<{ class ImageGridHeader extends PureComponent<{
title: string, title: string;
subtitle: string, subtitle: string;
onClear: ?() => void, onClear: (() => void) | undefined;
}> { }> {
static Container = styled(FlexRow)({ static Container = styled(FlexRow)({
color: colors.dark70, color: colors.dark70,
@@ -384,20 +385,20 @@ class ImageGridHeader extends PureComponent<{
} }
class ImageItem extends PureComponent<{ class ImageItem extends PureComponent<{
imageId: ImageId, imageId: ImageId;
image: ?ImageData, image: ImageData;
selected: boolean, selected: boolean;
onSelected: (image: ImageId) => void, onSelected: (image: ImageId) => void;
size: number, size: number;
numberOfRequests: number, numberOfRequests: number;
}> { }> {
static Container = styled(FlexBox)(({size}) => ({ static Container = styled(FlexBox)((props: {size: number}) => ({
float: 'left', float: 'left',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
flexShrink: 0, flexShrink: 0,
height: size, height: props.size,
width: size, width: props.size,
borderRadius: 4, borderRadius: 4,
marginRight: 15, marginRight: 15,
marginBottom: 15, marginBottom: 15,
@@ -415,7 +416,7 @@ class ImageItem extends PureComponent<{
padding: '0 0', padding: '0 0',
}); });
static SelectedHighlight = styled('div')(props => ({ static SelectedHighlight = styled('div')((props: {selected: boolean}) => ({
borderColor: colors.highlight, borderColor: colors.highlight,
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: props.selected ? 3 : 0, borderWidth: props.selected ? 3 : 0,
@@ -428,23 +429,25 @@ class ImageItem extends PureComponent<{
top: 0, top: 0,
})); }));
static HoverOverlay = styled(FlexColumn)(props => ({ static HoverOverlay = styled(FlexColumn)(
alignItems: 'center', (props: {selected: boolean; size: number}) => ({
backgroundColor: colors.whiteAlpha80, alignItems: 'center',
bottom: props.selected ? 4 : 0, backgroundColor: colors.whiteAlpha80,
fontSize: props.size > 100 ? 16 : 11, bottom: props.selected ? 4 : 0,
justifyContent: 'center', fontSize: props.size > 100 ? 16 : 11,
left: props.selected ? 4 : 0, justifyContent: 'center',
opacity: 0, left: props.selected ? 4 : 0,
position: 'absolute', opacity: 0,
right: props.selected ? 4 : 0, position: 'absolute',
top: props.selected ? 4 : 0, right: props.selected ? 4 : 0,
overflow: 'hidden', top: props.selected ? 4 : 0,
transition: '.1s opacity', overflow: 'hidden',
'&:hover': { transition: '.1s opacity',
opacity: 1, '&:hover': {
}, opacity: 1,
})); },
}),
);
static MemoryLabel = styled('span')({ static MemoryLabel = styled('span')({
fontWeight: 600, fontWeight: 600,

View File

@@ -7,8 +7,8 @@
* @format * @format
*/ */
import type {ImageData} from './api.js'; import {ImageData} from './api';
import type {ImageEventWithId} from './index.js'; import {ImageEventWithId} from './index';
import { import {
Component, Component,
DataDescription, DataDescription,
@@ -20,10 +20,11 @@ import {
colors, colors,
styled, styled,
} from 'flipper'; } from 'flipper';
import React from 'react';
type ImagesSidebarProps = { type ImagesSidebarProps = {
image: ?ImageData, image: ImageData;
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>;
}; };
type ImagesSidebarState = {}; type ImagesSidebarState = {};
@@ -38,7 +39,7 @@ const WordBreakFlexColumn = styled(FlexColumn)({
export default class ImagesSidebar extends Component< export default class ImagesSidebar extends Component<
ImagesSidebarProps, ImagesSidebarProps,
ImagesSidebarState, ImagesSidebarState
> { > {
render() { render() {
return ( return (
@@ -81,7 +82,7 @@ export default class ImagesSidebar extends Component<
} }
class EventDetails extends Component<{ class EventDetails extends Component<{
event: ImageEventWithId, event: ImageEventWithId;
}> { }> {
render() { render() {
const {event} = this.props; const {event} = this.props;
@@ -158,9 +159,9 @@ class EventDetails extends Component<{
} }
class RequestHeader extends Component<{ class RequestHeader extends Component<{
event: ImageEventWithId, event: ImageEventWithId;
}> { }> {
dateString = timestamp => { dateString = (timestamp: number) => {
const date = new Date(timestamp); const date = new Date(timestamp);
return `${date.toTimeString().split(' ')[0]}.${( return `${date.toTimeString().split(' ')[0]}.${(
'000' + date.getMilliseconds() '000' + date.getMilliseconds()

View File

@@ -8,7 +8,7 @@
*/ */
import {Block, Button, colors, FlexColumn, styled, Glyph} from 'flipper'; import {Block, Button, colors, FlexColumn, styled, Glyph} from 'flipper';
import React, {Component} from 'react'; import React, {ChangeEvent, Component} from 'react';
const Container = styled(Block)({ const Container = styled(Block)({
position: 'relative', position: 'relative',
@@ -54,26 +54,26 @@ const StyledGlyph = styled(Glyph)({
}); });
type State = { type State = {
visibleList: boolean, visibleList: boolean;
}; };
export default class MultipleSelect extends Component< export default class MultipleSelect extends Component<
{ {
selected: Set<string>, selected: Set<string>;
options: Set<string>, options: Set<string>;
onChange: (selectedItem: string, checked: boolean) => void, onChange: (selectedItem: string, checked: boolean) => void;
label: string, label: string;
}, },
State, State
> { > {
state = { state = {
visibleList: false, visibleList: false,
}; };
handleOnChange = (event: SyntheticInputEvent<HTMLInputElement>) => { handleOnChange = (event: ChangeEvent<HTMLInputElement>) => {
const { const {
target: {value, checked}, target: {value, checked},
} = event; } = event;

View File

@@ -7,23 +7,26 @@
* @format * @format
*/ */
import FrescoPlugin from '../index.js'; import FrescoPlugin from '../index';
import type {PersistedState, ImageEventWithId} from '../index.js'; import {PersistedState, ImageEventWithId} from '../index';
import type {AndroidCloseableReferenceLeakEvent} from '../api.js'; import {AndroidCloseableReferenceLeakEvent} from '../api';
import type {MetricType} from 'flipper'; import {MetricType} from 'flipper';
import type {Notification} from '../../../plugin.tsx'; import {Notification} from '../../../plugin';
import {ImagesMap} from '../ImagePool';
type ScanDisplayTime = {[scan_number: number]: number};
function mockPersistedState( function mockPersistedState(
imageSizes: Array<{ imageSizes: Array<{
width: number, width: number;
height: number, height: number;
}> = [], }> = [],
viewport: { viewport: {
width: number, width: number;
height: number, height: number;
} = {width: 150, height: 150}, } = {width: 150, height: 150},
): PersistedState { ): PersistedState {
const scanDisplayTime = {}; const scanDisplayTime: ScanDisplayTime = {};
scanDisplayTime[1] = 3; scanDisplayTime[1] = 3;
const events: Array<ImageEventWithId> = [ const events: Array<ImageEventWithId> = [
{ {
@@ -38,16 +41,19 @@ function mockPersistedState(
}, },
]; ];
const imagesMap = imageSizes.reduce((acc, val, index) => { const imagesMap = imageSizes.reduce(
acc[index] = { (acc, val, index) => {
imageId: String(index), acc[index] = {
width: val.width, imageId: String(index),
height: val.height, width: val.width,
sizeBytes: 10, height: val.height,
data: undefined, sizeBytes: 10,
}; data: 'undefined',
return acc; };
}, {}); return acc;
},
{} as ImagesMap,
);
return { return {
surfaceList: new Set(), surfaceList: new Set(),
@@ -184,9 +190,8 @@ test('the metric reducer with the no viewPort data in events', () => {
const metrics = metricsReducer(persistedState); const metrics = metricsReducer(persistedState);
return expect(metrics).resolves.toMatchObject({WASTED_BYTES: 0}); return expect(metrics).resolves.toMatchObject({WASTED_BYTES: 0});
}); });
test('the metric reducer with the multiple events', () => { test('the metric reducer with the multiple events', () => {
const scanDisplayTime = {}; const scanDisplayTime: ScanDisplayTime = {};
scanDisplayTime[1] = 3; scanDisplayTime[1] = 3;
const events: Array<ImageEventWithId> = [ const events: Array<ImageEventWithId> = [
{ {
@@ -228,22 +233,27 @@ test('the metric reducer with the multiple events', () => {
height: 300, height: 300,
}, },
]; ];
const imagesMap = imageSizes.reduce((acc, val, index) => { const imagesMap = imageSizes.reduce(
acc[index] = { (acc, val, index) => {
imageId: String(index), acc[index] = {
width: val.width, imageId: String(index),
height: val.height, width: val.width,
sizeBytes: 10, height: val.height,
data: undefined, sizeBytes: 10,
}; data: 'undefined',
return acc; };
}, {}); return acc;
},
{} as ImagesMap,
);
const persistedState = { const persistedState = {
surfaceList: new Set(), surfaceList: new Set<string>(),
images: [], images: [],
nextEventId: 0, nextEventId: 0,
events, events,
imagesMap, imagesMap,
closeableReferenceLeaks: [],
isLeakTrackingEnabled: true,
}; };
const metricsReducer = FrescoPlugin.metricsReducer; const metricsReducer = FrescoPlugin.metricsReducer;
expect(metricsReducer).toBeDefined(); expect(metricsReducer).toBeDefined();
@@ -255,7 +265,7 @@ test('the metric reducer with the multiple events', () => {
test('closeable reference metrics on empty state', () => { test('closeable reference metrics on empty state', () => {
const metricsReducer: ( const metricsReducer: (
persistedState: PersistedState, persistedState: PersistedState,
) => Promise<MetricType> = (FrescoPlugin.metricsReducer: any); ) => Promise<MetricType> = FrescoPlugin.metricsReducer;
const persistedState = mockPersistedState(); const persistedState = mockPersistedState();
const metrics = metricsReducer(persistedState); const metrics = metricsReducer(persistedState);
return expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 0}); return expect(metrics).resolves.toMatchObject({CLOSEABLE_REFERENCE_LEAKS: 0});
@@ -264,7 +274,7 @@ test('closeable reference metrics on empty state', () => {
test('closeable reference metrics on input', () => { test('closeable reference metrics on input', () => {
const metricsReducer: ( const metricsReducer: (
persistedState: PersistedState, persistedState: PersistedState,
) => Promise<MetricType> = (FrescoPlugin.metricsReducer: any); ) => Promise<MetricType> = FrescoPlugin.metricsReducer;
const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [ const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [
{ {
identityHashCode: 'deadbeef', identityHashCode: 'deadbeef',
@@ -288,7 +298,7 @@ test('closeable reference metrics on input', () => {
test('notifications for leaks', () => { test('notifications for leaks', () => {
const notificationReducer: ( const notificationReducer: (
persistedState: PersistedState, persistedState: PersistedState,
) => Array<Notification> = (FrescoPlugin.getActiveNotifications: any); ) => Array<Notification> = FrescoPlugin.getActiveNotifications;
const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [ const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [
{ {
identityHashCode: 'deadbeef', identityHashCode: 'deadbeef',

View File

@@ -1,77 +0,0 @@
/**
* 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 type ImageId = string;
// Listing images
export type CacheInfo = {|
cacheType: string,
clearKey?: null, // set this if this cache level supports clear(<key>)
sizeBytes: number,
maxSizeBytes?: number,
imageIds: Array<ImageId>,
|};
export type ImagesList = Array<CacheInfo>;
// The iOS Flipper api does not support a top-level array, so we wrap it in an object
export type ImagesListResponse = {|
levels: ImagesList,
|};
// listImages() -> ImagesListResponse
// Getting details on a specific image
export type ImageBytes = string;
export type ImageData = {|
imageId: ImageId,
uri?: string,
width: number,
height: number,
sizeBytes: number,
data: ImageBytes,
surface?: string,
|};
// getImage({imageId: string}) -> ImageData
// Subscribing to image events (requests and prefetches)
export type Timestamp = number;
export type ViewportData = {|
width: number,
height: number,
scanDisplayTime: {[scan_number: number]: Timestamp},
|};
export type ImageEvent = {
imageIds: Array<ImageId>,
attribution: Array<string>,
startTime: Timestamp,
endTime: Timestamp,
source: string,
coldStart: boolean,
viewport?: ViewportData, // not set for prefetches
};
// Misc
export type FrescoDebugOverlayEvent = {|
enabled: boolean,
|};
export type AndroidCloseableReferenceLeakEvent = {|
identityHashCode: string,
className: string,
stacktrace: ?string,
|};

View File

@@ -0,0 +1,77 @@
/**
* 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 type ImageId = string;
// Listing images
export type CacheInfo = {
cacheType: string;
clearKey?: string; // set this if this cache level supports clear(<key>)
sizeBytes: number;
maxSizeBytes?: number;
imageIds: Array<ImageId>;
};
export type ImagesList = Array<CacheInfo>;
// The iOS Flipper api does not support a top-level array, so we wrap it in an object
export type ImagesListResponse = {
levels: ImagesList;
};
// listImages() -> ImagesListResponse
// Getting details on a specific image
export type ImageBytes = string;
export type ImageData = {
imageId: ImageId;
uri?: string;
width: number;
height: number;
sizeBytes: number;
data: ImageBytes;
surface?: string;
};
// getImage({imageId: string}) -> ImageData
// Subscribing to image events (requests and prefetches)
export type Timestamp = number;
export type ViewportData = {
width: number;
height: number;
scanDisplayTime: {[scan_number: number]: Timestamp};
};
export type ImageEvent = {
imageIds: Array<ImageId>;
attribution: Array<string>;
startTime: Timestamp;
endTime: Timestamp;
source: string;
coldStart: boolean;
viewport?: ViewportData; // not set for prefetches
};
// Misc
export type FrescoDebugOverlayEvent = {
enabled: boolean;
};
export type AndroidCloseableReferenceLeakEvent = {
identityHashCode: string;
className: string;
stacktrace: string | null;
};

View File

@@ -7,7 +7,7 @@
* @format * @format
*/ */
import type { import {
ImageId, ImageId,
ImageData, ImageData,
ImagesList, ImagesList,
@@ -16,12 +16,12 @@ import type {
FrescoDebugOverlayEvent, FrescoDebugOverlayEvent,
AndroidCloseableReferenceLeakEvent, AndroidCloseableReferenceLeakEvent,
CacheInfo, CacheInfo,
} from './api.js'; } from './api';
import {Fragment} from 'react'; import {Fragment} from 'react';
import type {ImagesMap} from './ImagePool.js'; import {ImagesMap} from './ImagePool';
import type {MetricType, MiddlewareAPI} from 'flipper'; import {MetricType, MiddlewareAPI} from 'flipper';
import React from 'react'; import React from 'react';
import ImagesCacheOverview from './ImagesCacheOverview.js'; import ImagesCacheOverview from './ImagesCacheOverview';
import { import {
FlipperPlugin, FlipperPlugin,
FlexRow, FlexRow,
@@ -31,29 +31,29 @@ import {
styled, styled,
isProduction, isProduction,
} from 'flipper'; } from 'flipper';
import ImagesSidebar from './ImagesSidebar.js'; import ImagesSidebar from './ImagesSidebar';
import ImagePool from './ImagePool.js'; import ImagePool from './ImagePool';
import type {Notification} from '../../plugin.tsx'; import {Notification, BaseAction} from '../../plugin';
export type ImageEventWithId = ImageEvent & {eventId: number}; export type ImageEventWithId = ImageEvent & {eventId: number};
export type PersistedState = { export type PersistedState = {
surfaceList: Set<string>, surfaceList: Set<string>;
images: ImagesList, images: ImagesList;
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>;
imagesMap: ImagesMap, imagesMap: ImagesMap;
closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent>, closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent>;
isLeakTrackingEnabled: boolean, isLeakTrackingEnabled: boolean;
nextEventId: number, nextEventId: number;
}; };
type PluginState = { type PluginState = {
selectedSurfaces: Set<string>, selectedSurfaces: Set<string>;
selectedImage: ?ImageId, selectedImage: ImageId | null;
isDebugOverlayEnabled: boolean, isDebugOverlayEnabled: boolean;
isAutoRefreshEnabled: boolean, isAutoRefreshEnabled: boolean;
images: ImagesList, images: ImagesList;
coldStartFilter: boolean, coldStartFilter: boolean;
}; };
const EmptySidebar = styled(FlexRow)({ const EmptySidebar = styled(FlexRow)({
@@ -70,23 +70,23 @@ export const InlineFlexRow = styled(FlexRow)({
const surfaceDefaultText = 'SELECT ALL SURFACES'; const surfaceDefaultText = 'SELECT ALL SURFACES';
const debugLog = (...args) => { const debugLog = (...args: any[]) => {
if (!isProduction()) { if (!isProduction()) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(...args); console.log(...args);
} }
}; };
type ImagesMetaData = {| type ImagesMetaData = {
levels: ImagesListResponse, levels: ImagesListResponse;
events: Array<ImageEventWithId>, events: Array<ImageEventWithId>;
imageDataList: Array<ImageData>, imageDataList: Array<ImageData>;
|}; };
export default class FlipperImagesPlugin extends FlipperPlugin< export default class FlipperImagesPlugin extends FlipperPlugin<
PluginState, PluginState,
*, BaseAction,
PersistedState, PersistedState
> { > {
static defaultPersistedState: PersistedState = { static defaultPersistedState: PersistedState = {
images: [], images: [],
@@ -99,10 +99,10 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
}; };
static exportPersistedState = ( static exportPersistedState = (
callClient: (string, ?Object) => Promise<Object>, callClient: (method: string, params?: any) => Promise<any>,
persistedState: ?PersistedState, persistedState: PersistedState,
store: ?MiddlewareAPI, store?: MiddlewareAPI,
): Promise<?PersistedState> => { ): Promise<PersistedState> => {
const defaultPromise = Promise.resolve(persistedState); const defaultPromise = Promise.resolve(persistedState);
if (!persistedState) { if (!persistedState) {
persistedState = FlipperImagesPlugin.defaultPersistedState; persistedState = FlipperImagesPlugin.defaultPersistedState;
@@ -149,9 +149,9 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
acc.add(id); acc.add(id);
}); });
return acc; return acc;
}, new Set()); }, new Set<string>());
const imageDataList: Array<ImageData> = []; const imageDataList: Array<ImageData> = [];
for (const id: string of idSet) { for (const id of idSet) {
try { try {
const imageData: ImageData = await callClient('getImage', { const imageData: ImageData = await callClient('getImage', {
imageId: id, imageId: id,
@@ -173,10 +173,10 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
static persistedStateReducer = ( static persistedStateReducer = (
persistedState: PersistedState, persistedState: PersistedState,
method: string, method: string,
data: Object, data: AndroidCloseableReferenceLeakEvent | ImageEvent,
): PersistedState => { ): PersistedState => {
if (method == 'closeable_reference_leak_event') { if (method == 'closeable_reference_leak_event') {
const event: AndroidCloseableReferenceLeakEvent = data; const event: AndroidCloseableReferenceLeakEvent = data as AndroidCloseableReferenceLeakEvent;
return { return {
...persistedState, ...persistedState,
closeableReferenceLeaks: persistedState.closeableReferenceLeaks.concat( closeableReferenceLeaks: persistedState.closeableReferenceLeaks.concat(
@@ -184,8 +184,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
), ),
}; };
} else if (method == 'events') { } else if (method == 'events') {
const event: ImageEvent = data; const event: ImageEvent = data as ImageEvent;
debugLog('Received events', event); debugLog('Received events', event);
const {surfaceList} = persistedState; const {surfaceList} = persistedState;
const {attribution} = event; const {attribution} = event;
@@ -267,11 +266,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
category: 'closeablereference_leak', category: 'closeablereference_leak',
})); }));
state: PluginState; state: PluginState = {
imagePool: ImagePool;
nextEventId: number = 1;
state = {
selectedSurfaces: new Set([surfaceDefaultText]), selectedSurfaces: new Set([surfaceDefaultText]),
selectedImage: null, selectedImage: null,
isDebugOverlayEnabled: false, isDebugOverlayEnabled: false,
@@ -279,6 +274,8 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
images: [], images: [],
coldStartFilter: false, coldStartFilter: false,
}; };
imagePool: ImagePool | undefined;
nextEventId: number = 1;
filterImages = ( filterImages = (
images: ImagesList, images: ImagesList,
@@ -340,7 +337,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
} }
teardown() { teardown() {
this.imagePool.clear(); this.imagePool ? this.imagePool.clear() : undefined;
} }
updateImagesOnUI = ( updateImagesOnUI = (
@@ -365,7 +362,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
debugLog('Requesting images list (reason=' + reason + ')'); debugLog('Requesting images list (reason=' + reason + ')');
this.client.call('listImages').then((response: ImagesListResponse) => { this.client.call('listImages').then((response: ImagesListResponse) => {
response.levels.forEach(data => response.levels.forEach(data =>
this.imagePool.fetchImages(data.imageIds), this.imagePool ? this.imagePool.fetchImages(data.imageIds) : undefined,
); );
this.props.setPersistedState({images: response.levels}); this.props.setPersistedState({images: response.levels});
this.updateImagesOnUI( this.updateImagesOnUI(
@@ -409,7 +406,7 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
debugLog('<- getImage requested for ' + imageId); debugLog('<- getImage requested for ' + imageId);
this.client.call('getImage', {imageId}).then((image: ImageData) => { this.client.call('getImage', {imageId}).then((image: ImageData) => {
debugLog('-> getImage ' + imageId + ' returned'); debugLog('-> getImage ' + imageId + ' returned');
this.imagePool._fetchCompleted(image); this.imagePool ? this.imagePool._fetchCompleted(image) : undefined;
}); });
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "Fresco", "name": "Fresco",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.tsx",
"license": "MIT", "license": "MIT",
"keywords": ["flipper-plugin"], "keywords": ["flipper-plugin"],
"title": "Images", "title": "Images",