Files
flipper/desktop/plugins/seamammals/src/index.tsx
Michel Weststrate 7c6065889d support createPaste
Summary:
Added support for `createPaste` in Sandy plugins

Nice minimalistic example of how to expose a Flipper api to Sandy.

Note that some indirection could be removed by having an interface that is shared directly between `BasePluginClient` and `FlipperLib` (e.g. `PublicFlipperLib`). In contrast to `addMenuEntries` from the previous diff, `createPaste` is basically exposed verbatim to Sandy without additional wrapping, so those cases could be made simpler. Maybe will do that later.

Reviewed By: passy

Differential Revision: D22815873

fbshipit-source-id: e6d0773a35341edfe5de0898317eaadf88de79d0
2020-08-04 07:47:14 -07:00

166 lines
3.4 KiB
TypeScript

/**
* 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 {
Text,
Panel,
ManagedDataInspector,
DetailSidebar,
FlexRow,
FlexColumn,
styled,
colors,
} from 'flipper';
import React, {memo} from 'react';
import {FlipperClient, usePlugin, createState, useValue} from 'flipper-plugin';
type Id = number;
type Row = {
id: Id;
title: string;
url: string;
};
type Events = {
newRow: Row;
};
function renderSidebar(row: Row) {
return (
<Panel floating={false} heading={'Extras'}>
<ManagedDataInspector data={row} expandRoot={true} />
</Panel>
);
}
type PersistedState = {
[key: string]: Row;
};
export function plugin(client: FlipperClient<Events, {}>) {
const rows = createState<PersistedState>({}, {persist: 'rows'});
const selectedID = createState<string | null>(null, {persist: 'selection'});
client.addMenuEntry(
{
label: 'Reset Selection',
topLevelMenu: 'Edit',
handler: () => {
selectedID.set(null);
},
},
{
action: 'createPaste',
handler: async () => {
const selection = selectedID.get();
if (selection) {
const url = await client.createPaste(
JSON.stringify(rows.get()[selection], null, 2),
);
alert(url); // TODO: use notifications T69990351
}
},
},
);
client.onMessage('newRow', (row) => {
rows.update((draft) => {
draft[row.id] = row;
});
});
function setSelection(id: number) {
selectedID.set('' + id);
}
return {
rows,
selectedID,
setSelection,
};
}
export function Component() {
const instance = usePlugin(plugin);
const selectedID = useValue(instance.selectedID);
const rows = useValue(instance.rows);
return (
<Container>
{Object.entries(rows).map(([id, row]) => (
<Card
row={row}
onSelect={instance.setSelection}
selected={id === selectedID}
key={id}
/>
))}
<DetailSidebar>
{selectedID && renderSidebar(rows[selectedID])}
</DetailSidebar>
</Container>
);
}
type CardProps = {
onSelect: (id: number) => void;
selected: boolean;
row: Row;
};
const Card = memo(({row, selected, onSelect}: CardProps) => {
return (
<CardContainer
data-testid={row.title}
onClick={() => onSelect(row.id)}
selected={selected}>
<Image style={{backgroundImage: `url(${row.url || ''})`}} />
<Title>{row.title}</Title>
</CardContainer>
);
});
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',
});