diff --git a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx
index 53cd5d7bf..41a601c43 100644
--- a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx
+++ b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx
@@ -77,6 +77,5 @@ export function Component() {
// @ts-expect-error
api.bla;
- // TODO N.b.: state updates won't be visible
return
Hi from test plugin {count}
;
}
diff --git a/desktop/plugins/seamammals/package.json b/desktop/plugins/seamammals/package.json
index 4dca8562a..ed89335c7 100644
--- a/desktop/plugins/seamammals/package.json
+++ b/desktop/plugins/seamammals/package.json
@@ -23,7 +23,8 @@
"prepack": "flipper-pkg lint && flipper-pkg bundle --production"
},
"peerDependencies": {
- "flipper": "0.49.0"
+ "flipper": "0.49.0",
+ "flipper-plugin": "0.49.0"
},
"devDependencies": {
"flipper": "0.49.0",
diff --git a/desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx b/desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx
new file mode 100644
index 000000000..41a0394a8
--- /dev/null
+++ b/desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx
@@ -0,0 +1,122 @@
+/**
+ * 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 * as React from 'react';
+import * as Flipper from 'flipper';
+// eslint-disable-next-line
+import {act} from '@testing-library/react';
+
+{
+ // These mocks are needed because seammammals still uses Flipper in its UI implementation,
+ // so we need to mock some things
+
+ // @ts-ignore
+ jest.spyOn(Flipper.DetailSidebar, 'type').mockImplementation((props) => {
+ return {props.children}
;
+ });
+
+ const origRequestIdleCallback = window.requestIdleCallback;
+ const origCancelIdleCallback = window.cancelIdleCallback;
+ // @ts-ignore
+ window.requestIdleCallback = (fn: () => void) => {
+ // the synchronous implementation forces DataInspector to render in sync
+ act(fn);
+ };
+ // @ts-ignore
+ window.cancelIdleCallback = clearImmediate;
+ afterAll(() => {
+ window.requestIdleCallback = origRequestIdleCallback;
+ window.cancelIdleCallback = origCancelIdleCallback;
+ });
+}
+
+import {TestUtils} from 'flipper-plugin';
+import * as MammalsPlugin from '../';
+
+test('It can store rows', () => {
+ const {instance, ...plugin} = TestUtils.startPlugin(MammalsPlugin);
+
+ expect(instance.rows.get()).toEqual({});
+ expect(instance.selectedID.get()).toBeNull();
+
+ plugin.sendEvent('newRow', {
+ id: 1,
+ title: 'Dolphin',
+ url: 'http://dolphin.png',
+ });
+ plugin.sendEvent('newRow', {
+ id: 2,
+ title: 'Turtle',
+ url: 'http://turtle.png',
+ });
+
+ expect(instance.rows.get()).toMatchInlineSnapshot(`
+ Object {
+ "1": Object {
+ "id": 1,
+ "title": "Dolphin",
+ "url": "http://dolphin.png",
+ },
+ "2": Object {
+ "id": 2,
+ "title": "Turtle",
+ "url": "http://turtle.png",
+ },
+ }
+ `);
+});
+
+test('It can have selection and render details', async () => {
+ const {instance, renderer, act, ...plugin} = TestUtils.renderPlugin(
+ MammalsPlugin,
+ );
+
+ expect(instance.rows.get()).toEqual({});
+ expect(instance.selectedID.get()).toBeNull();
+
+ plugin.sendEvent('newRow', {
+ id: 1,
+ title: 'Dolphin',
+ url: 'http://dolphin.png',
+ });
+ plugin.sendEvent('newRow', {
+ id: 2,
+ title: 'Turtle',
+ url: 'http://turtle.png',
+ });
+
+ // Dolphin card should now be visible
+ expect(await renderer.findByTestId('Dolphin')).not.toBeNull();
+ // Let's assert the structure of the Turtle card as well
+ expect(await renderer.findByTestId('Turtle')).toMatchInlineSnapshot(`
+
+ `);
+ // Nothing selected, so we should not have a sidebar
+ expect(renderer.queryAllByText('Extras').length).toBe(0);
+
+ act(() => {
+ instance.setSelection(2);
+ });
+
+ // Sidebar should be visible now
+ expect(await renderer.findByText('Extras')).not.toBeNull();
+});
diff --git a/desktop/plugins/seamammals/src/index.tsx b/desktop/plugins/seamammals/src/index.tsx
index 559a37a5e..174befdd4 100644
--- a/desktop/plugins/seamammals/src/index.tsx
+++ b/desktop/plugins/seamammals/src/index.tsx
@@ -11,14 +11,15 @@ import {
Text,
Panel,
ManagedDataInspector,
- FlipperPlugin,
DetailSidebar,
FlexRow,
FlexColumn,
styled,
colors,
} from 'flipper';
-import React from 'react';
+import React, {memo} from 'react';
+
+import {FlipperClient, usePlugin, createState, useValue} from 'flipper-plugin';
type Id = number;
@@ -28,6 +29,10 @@ type Row = {
url: string;
};
+type Events = {
+ newRow: Row;
+};
+
function renderSidebar(row: Row) {
return (
@@ -44,105 +49,99 @@ type PersistedState = {
[key: string]: Row;
};
-export default class SeaMammals extends FlipperPlugin<
- State,
- any,
- PersistedState
-> {
- static defaultPersistedState = {};
+export function plugin(client: FlipperClient) {
+ const rows = createState({});
+ const selectedID = createState(null);
- static persistedStateReducer(
- persistedState: PersistedState,
- method: string,
- payload: Row,
- ) {
- if (method === 'newRow') {
- return Object.assign({}, persistedState, {
- [payload.id]: payload,
- });
- }
- return persistedState;
- }
-
- static Container = styled(FlexRow)({
- backgroundColor: colors.macOSTitleBarBackgroundBlur,
- flexWrap: 'wrap',
- alignItems: 'flex-start',
- alignContent: 'flex-start',
- flexGrow: 1,
- overflow: 'scroll',
+ client.onMessage('newRow', (row) => {
+ rows.update((draft) => {
+ draft[row.id] = row;
+ });
});
- state = {
- selectedID: null as string | null,
+ function setSelection(id: number) {
+ selectedID.set('' + id);
+ }
+
+ return {
+ rows,
+ selectedID,
+ setSelection,
};
-
- render() {
- const {selectedID} = this.state;
- const {persistedState} = this.props;
-
- return (
-
- {Object.entries(persistedState).map(([id, row]) => (
- this.setState({selectedID: id})}
- selected={id === selectedID}
- key={id}
- />
- ))}
-
- {selectedID && renderSidebar(persistedState[selectedID])}
-
-
- );
- }
}
-class Card extends React.Component<
- {
- onSelect: () => void;
- selected: boolean;
- } & Row
-> {
- static Container = styled(FlexColumn)<{selected?: boolean}>((props) => ({
- margin: 10,
- borderRadius: 5,
- border: '2px solid black',
- backgroundColor: colors.white,
- borderColor: props.selected
- ? colors.macOSTitleBarIconSelected
- : colors.white,
- padding: 0,
- width: 150,
- overflow: 'hidden',
- boxShadow: '1px 1px 4px rgba(0,0,0,0.1)',
- cursor: 'pointer',
- }));
+export function Component() {
+ const instance = usePlugin(plugin);
+ const selectedID = useValue(instance.selectedID);
+ const rows = useValue(instance.rows);
- static Image = styled.div({
- backgroundSize: 'cover',
- width: '100%',
- paddingTop: '100%',
- });
-
- static Title = styled(Text)({
- fontSize: 14,
- fontWeight: 'bold',
- padding: '10px 5px',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- });
-
- render() {
- return (
-
-
- {this.props.title}
-
- );
- }
+ return (
+
+ {Object.entries(rows).map(([id, row]) => (
+
+ ))}
+
+ {selectedID && renderSidebar(rows[selectedID])}
+
+
+ );
}
+
+type CardProps = {
+ onSelect: (id: number) => void;
+ selected: boolean;
+ row: Row;
+};
+const Card = memo(({row, selected, onSelect}: CardProps) => {
+ return (
+ onSelect(row.id)}
+ selected={selected}>
+
+ {row.title}
+
+ );
+});
+
+const Container = styled(FlexRow)({
+ backgroundColor: colors.macOSTitleBarBackgroundBlur,
+ flexWrap: 'wrap',
+ alignItems: 'flex-start',
+ alignContent: 'flex-start',
+ flexGrow: 1,
+ overflow: 'scroll',
+});
+
+const CardContainer = styled(FlexColumn)<{selected?: boolean}>((props) => ({
+ margin: 10,
+ borderRadius: 5,
+ border: '2px solid black',
+ backgroundColor: colors.white,
+ borderColor: props.selected ? colors.macOSTitleBarIconSelected : colors.white,
+ padding: 0,
+ width: 150,
+ overflow: 'hidden',
+ boxShadow: '1px 1px 4px rgba(0,0,0,0.1)',
+ cursor: 'pointer',
+}));
+
+const Image = styled.div({
+ backgroundSize: 'cover',
+ width: '100%',
+ paddingTop: '100%',
+});
+
+const Title = styled(Text)({
+ fontSize: 14,
+ fontWeight: 'bold',
+ padding: '10px 5px',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+});
diff --git a/iOS/Tutorial/Podfile.lock b/iOS/Tutorial/Podfile.lock
index 94117f405..0a5571670 100644
--- a/iOS/Tutorial/Podfile.lock
+++ b/iOS/Tutorial/Podfile.lock
@@ -5,7 +5,7 @@ PODS:
- ComponentKit (0.30):
- RenderCore (= 0.30)
- Yoga (~> 1.14)
- - Flipper (0.48.0):
+ - Flipper (0.49.0):
- Flipper-Folly (~> 2.2)
- Flipper-RSocket (~> 1.1)
- Flipper-DoubleConversion (1.1.7)
@@ -19,41 +19,41 @@ PODS:
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.1.0):
- Flipper-Folly (~> 2.2)
- - FlipperKit (0.48.0):
- - FlipperKit/Core (= 0.48.0)
- - FlipperKit/Core (0.48.0):
- - Flipper (~> 0.48.0)
+ - FlipperKit (0.49.0):
+ - FlipperKit/Core (= 0.49.0)
+ - FlipperKit/Core (0.49.0):
+ - Flipper (~> 0.49.0)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- - FlipperKit/CppBridge (0.48.0):
- - Flipper (~> 0.48.0)
- - FlipperKit/FBCxxFollyDynamicConvert (0.48.0):
+ - FlipperKit/CppBridge (0.49.0):
+ - Flipper (~> 0.49.0)
+ - FlipperKit/FBCxxFollyDynamicConvert (0.49.0):
- Flipper-Folly (~> 2.2)
- - FlipperKit/FBDefines (0.48.0)
- - FlipperKit/FKPortForwarding (0.48.0):
+ - FlipperKit/FBDefines (0.49.0)
+ - FlipperKit/FKPortForwarding (0.49.0):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- - FlipperKit/FlipperKitHighlightOverlay (0.48.0)
- - FlipperKit/FlipperKitLayoutComponentKitSupport (0.48.0):
+ - FlipperKit/FlipperKitHighlightOverlay (0.49.0)
+ - FlipperKit/FlipperKitLayoutComponentKitSupport (0.49.0):
- ComponentKit (~> 0.30)
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutPlugin
- FlipperKit/FlipperKitLayoutTextSearchable
- RenderCore (~> 0.30)
- - FlipperKit/FlipperKitLayoutPlugin (0.48.0):
+ - FlipperKit/FlipperKitLayoutPlugin (0.49.0):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- - FlipperKit/FlipperKitLayoutTextSearchable (0.48.0)
- - FlipperKit/FlipperKitNetworkPlugin (0.48.0):
+ - FlipperKit/FlipperKitLayoutTextSearchable (0.49.0)
+ - FlipperKit/FlipperKitNetworkPlugin (0.49.0):
- FlipperKit/Core
- - FlipperKit/FlipperKitUserDefaultsPlugin (0.48.0):
+ - FlipperKit/FlipperKitUserDefaultsPlugin (0.49.0):
- FlipperKit/Core
- - FlipperKit/SKIOSNetworkPlugin (0.48.0):
+ - FlipperKit/SKIOSNetworkPlugin (0.49.0):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- OpenSSL-Universal (1.0.2.19):
@@ -65,10 +65,10 @@ PODS:
- Yoga (~> 1.14)
DEPENDENCIES:
- - FlipperKit (~> 0.48.0)
- - FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.48.0)
- - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.48.0)
- - FlipperKit/SKIOSNetworkPlugin (~> 0.48.0)
+ - FlipperKit (~> 0.49.0)
+ - FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.49.0)
+ - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.49.0)
+ - FlipperKit/SKIOSNetworkPlugin (~> 0.49.0)
SPEC REPOS:
trunk:
@@ -93,18 +93,18 @@ SPEC CHECKSUMS:
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
ComponentKit: c34da1ab3515cf18db68a4ba22c6599568d1de74
- Flipper: ff9243fb80717f8310290396bd9f79be7c1a8016
+ Flipper: 78dc06980f2a5f7dbf28371a2800955430a95302
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
- FlipperKit: a6208c63e203ceda87d99414755cc53615b749f3
+ FlipperKit: 26980c82fa2710f7ccc09eba394d1837258bd065
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
RenderCore: d779c47622b313ce2d51bb36d084517af38b0dc1
Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: db8648db517152c1225d37f97f0083975dfe8623
+PODFILE CHECKSUM: eee0bd8fb136f35ceb29ff650a6d04bfb903dad1
COCOAPODS: 1.9.3