Move DataDescription utility to flipper-plugin
Summary: Another utility component used by DataInspector. Colors have been hardcoded for now, to decouple from the classic Flipper color palette. Will organize this better later in this stack, and when addressing / adding support for dark mode. Buttons, checkboxes, selects have been replaced by Antd's counterpart. Unlike ManagedDataTable, the DataInspector is primarily moved rather than copied & adapted, as the underlying abstraction / API won't change significantly. So the changes here will immediately affect all plugins in Flipper using this component. Reviewed By: passy Differential Revision: D27603126 fbshipit-source-id: bacd48c9af2b591033e7f2352627f11acb4df589
This commit is contained in:
committed by
Facebook GitHub Bot
parent
b6674bf96b
commit
9030a98c6e
@@ -53,7 +53,6 @@
|
|||||||
"query-string": "^7.0.0",
|
"query-string": "^7.0.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-async": "^10.0.0",
|
"react-async": "^10.0.0",
|
||||||
"react-color": "^2.19.3",
|
|
||||||
"react-debounce-render": "^7.0.0",
|
"react-debounce-render": "^7.0.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-element-to-jsx-string": "^14.3.1",
|
"react-element-to-jsx-string": "^14.3.1",
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ export {default as DataInspector} from './ui/components/data-inspector/DataInspe
|
|||||||
export {default as ManagedDataInspector} from './ui/components/data-inspector/ManagedDataInspector';
|
export {default as ManagedDataInspector} from './ui/components/data-inspector/ManagedDataInspector';
|
||||||
export {default as SearchableDataInspector} from './ui/components/data-inspector/SearchableDataInspector';
|
export {default as SearchableDataInspector} from './ui/components/data-inspector/SearchableDataInspector';
|
||||||
export {
|
export {
|
||||||
default as DataDescription,
|
_DataDescription as DataDescription,
|
||||||
DataDescriptionType,
|
DataDescriptionType,
|
||||||
} from './ui/components/data-inspector/DataDescription';
|
} from 'flipper-plugin';
|
||||||
export {_HighlightManager as HighlightManager} from 'flipper-plugin';
|
export {_HighlightManager as HighlightManager} from 'flipper-plugin';
|
||||||
export {default as Tabs} from './ui/components/Tabs';
|
export {default as Tabs} from './ui/components/Tabs';
|
||||||
export {default as Tab} from './ui/components/Tab';
|
export {default as Tab} from './ui/components/Tab';
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ SelectMenu.displayName = 'Select:SelectMenu';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Dropdown to select from a list of options
|
* Dropdown to select from a list of options
|
||||||
|
* @deprecated use Select from antd instead: https://ant.design/components/select/
|
||||||
*/
|
*/
|
||||||
export default class Select extends Component<{
|
export default class Select extends Component<{
|
||||||
/** Additional className added to the element */
|
/** Additional className added to the element */
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import DataDescription from './DataDescription';
|
import {_DataDescription} from 'flipper-plugin';
|
||||||
import {MenuTemplate} from '../ContextMenu';
|
import {MenuTemplate} from '../ContextMenu';
|
||||||
import {memo, useMemo, useRef, useState, useEffect, useCallback} from 'react';
|
import {memo, useMemo, useRef, useState, useEffect, useCallback} from 'react';
|
||||||
import ContextMenu from '../ContextMenu';
|
import ContextMenu from '../ContextMenu';
|
||||||
@@ -562,7 +562,7 @@ const DataInspector: React.FC<DataInspectorProps> = memo(
|
|||||||
let descriptionOrPreview;
|
let descriptionOrPreview;
|
||||||
if (renderExpanded || !isExpandable) {
|
if (renderExpanded || !isExpandable) {
|
||||||
descriptionOrPreview = (
|
descriptionOrPreview = (
|
||||||
<DataDescription
|
<_DataDescription
|
||||||
path={path}
|
path={path}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
type={type}
|
type={type}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import DataDescription, {DataDescriptionType} from './DataDescription';
|
import {DataDescriptionType, _DataDescription} from 'flipper-plugin';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {getSortedKeys} from './utils';
|
import {getSortedKeys} from './utils';
|
||||||
import {PureComponent} from 'react';
|
import {PureComponent} from 'react';
|
||||||
@@ -79,7 +79,7 @@ export default class DataPreview extends PureComponent<{
|
|||||||
|
|
||||||
const {type, value} = res;
|
const {type, value} = res;
|
||||||
return (
|
return (
|
||||||
<DataDescription
|
<_DataDescription
|
||||||
key={index}
|
key={index}
|
||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export {
|
|||||||
export {default as DataInspector} from './components/data-inspector/DataInspector';
|
export {default as DataInspector} from './components/data-inspector/DataInspector';
|
||||||
export {default as ManagedDataInspector} from './components/data-inspector/ManagedDataInspector';
|
export {default as ManagedDataInspector} from './components/data-inspector/ManagedDataInspector';
|
||||||
export {default as SearchableDataInspector} from './components/data-inspector/SearchableDataInspector';
|
export {default as SearchableDataInspector} from './components/data-inspector/SearchableDataInspector';
|
||||||
export {default as DataDescription} from './components/data-inspector/DataDescription';
|
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
export {default as Tabs} from './components/Tabs';
|
export {default as Tabs} from './components/Tabs';
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@reach/observe-rect": "^1.2.0",
|
"@reach/observe-rect": "^1.2.0",
|
||||||
"immer": "^9.0.0",
|
"immer": "^9.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
"react-element-to-jsx-string": "^14.3.2",
|
"react-element-to-jsx-string": "^14.3.2",
|
||||||
"react-virtual": "^2.6.1"
|
"react-virtual": "^2.6.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ test('Correct top level API exposed', () => {
|
|||||||
expect(exposedTypes.sort()).toMatchInlineSnapshot(`
|
expect(exposedTypes.sort()).toMatchInlineSnapshot(`
|
||||||
Array [
|
Array [
|
||||||
"Atom",
|
"Atom",
|
||||||
|
"DataDescriptionType",
|
||||||
"DataTableColumn",
|
"DataTableColumn",
|
||||||
"DataTableManager",
|
"DataTableManager",
|
||||||
"DefaultKeyboardAction",
|
"DefaultKeyboardAction",
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ export {
|
|||||||
InteractiveProps as _InteractiveProps,
|
InteractiveProps as _InteractiveProps,
|
||||||
} from './ui/Interactive';
|
} from './ui/Interactive';
|
||||||
|
|
||||||
|
export {
|
||||||
|
DataDescription as _DataDescription,
|
||||||
|
DataDescriptionType,
|
||||||
|
} from './ui/data-inspector/DataDescription';
|
||||||
|
|
||||||
export {useMemoize} from './utils/useMemoize';
|
export {useMemoize} from './utils/useMemoize';
|
||||||
|
|
||||||
// It's not ideal that this exists in flipper-plugin sources directly,
|
// It's not ideal that this exists in flipper-plugin sources directly,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {render, fireEvent} from '@testing-library/react';
|
import {render, fireEvent} from '@testing-library/react';
|
||||||
import {TestUtils} from 'flipper-plugin';
|
import {TestUtils} from 'flipper-plugin';
|
||||||
import {sleep} from 'flipper-plugin/src/utils/sleep';
|
import {sleep} from '../../utils/sleep';
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {
|
import {
|
||||||
setGlobalInteractionReporter,
|
setGlobalInteractionReporter,
|
||||||
|
|||||||
@@ -7,19 +7,41 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Link from '../Link';
|
import {Typography, Popover, Input, Select, Checkbox} from 'antd';
|
||||||
import {DataInspectorSetValue} from './DataInspector';
|
// TODO: restore import {DataInspectorSetValue} from './DataInspector';
|
||||||
import {PureComponent} from 'react';
|
import {PureComponent} from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {SketchPicker, CompactPicker} from 'react-color';
|
import {SketchPicker, CompactPicker} from 'react-color';
|
||||||
import Popover from '../Popover';
|
|
||||||
import {colors} from '../colors';
|
|
||||||
import Input from '../Input';
|
|
||||||
import React, {KeyboardEvent} from 'react';
|
import React, {KeyboardEvent} from 'react';
|
||||||
import Glyph from '../Glyph';
|
import {HighlightContext} from '../Highlight';
|
||||||
import {_HighlightContext} from 'flipper-plugin';
|
import {parseColor} from '../../utils/parseColor';
|
||||||
import Select from '../Select';
|
// import TimelineDataDescription from './TimelineDataDescription'; TODO:
|
||||||
import TimelineDataDescription from './TimelineDataDescription';
|
import {theme} from '../theme';
|
||||||
|
import {EditOutlined} from '@ant-design/icons';
|
||||||
|
import type {CheckboxChangeEvent} from 'antd/lib/checkbox';
|
||||||
|
|
||||||
|
const {Link} = Typography;
|
||||||
|
|
||||||
|
type DataInspectorSetValue = (path: Array<string>, val: any) => void;
|
||||||
|
|
||||||
|
// Based on FIG UI Core, TODO: does that still makes sense?
|
||||||
|
const presetColors = Object.values({
|
||||||
|
blue: '#4267b2', // Blue - Active-state nav glyphs, nav bars, links, buttons
|
||||||
|
green: '#42b72a', // Green - Confirmation, success, commerce and status
|
||||||
|
red: '#FC3A4B', // Red - Badges, error states
|
||||||
|
blueGrey: '#5f6673', // Blue Grey
|
||||||
|
slate: '#b9cad2', // Slate
|
||||||
|
aluminum: '#a3cedf', // Aluminum
|
||||||
|
seaFoam: '#54c7ec', // Sea Foam
|
||||||
|
teal: '#6bcebb', // Teal
|
||||||
|
lime: '#a3ce71', // Lime
|
||||||
|
lemon: '#fcd872', // Lemon
|
||||||
|
orange: '#f7923b', // Orange
|
||||||
|
tomato: '#fb724b', // Tomato - Tometo? Tomato.
|
||||||
|
cherry: '#f35369', // Cherry
|
||||||
|
pink: '#ec7ebd', // Pink
|
||||||
|
grape: '#8c72cb', // Grape
|
||||||
|
});
|
||||||
|
|
||||||
const NullValue = styled.span({
|
const NullValue = styled.span({
|
||||||
color: 'rgb(128, 128, 128)',
|
color: 'rgb(128, 128, 128)',
|
||||||
@@ -32,13 +54,13 @@ const UndefinedValue = styled.span({
|
|||||||
UndefinedValue.displayName = 'DataDescription:UndefinedValue';
|
UndefinedValue.displayName = 'DataDescription:UndefinedValue';
|
||||||
|
|
||||||
const StringValue = styled.span({
|
const StringValue = styled.span({
|
||||||
color: colors.cherryDark1,
|
color: '#e04c60',
|
||||||
wordWrap: 'break-word',
|
wordWrap: 'break-word',
|
||||||
});
|
});
|
||||||
StringValue.displayName = 'DataDescription:StringValue';
|
StringValue.displayName = 'DataDescription:StringValue';
|
||||||
|
|
||||||
const ColorValue = styled.span({
|
const ColorValue = styled.span({
|
||||||
color: colors.blueGrey,
|
color: '#5f6673',
|
||||||
});
|
});
|
||||||
ColorValue.displayName = 'DataDescription:ColorValue';
|
ColorValue.displayName = 'DataDescription:ColorValue';
|
||||||
|
|
||||||
@@ -48,7 +70,7 @@ const SymbolValue = styled.span({
|
|||||||
SymbolValue.displayName = 'DataDescription:SymbolValue';
|
SymbolValue.displayName = 'DataDescription:SymbolValue';
|
||||||
|
|
||||||
const NumberValue = styled.span({
|
const NumberValue = styled.span({
|
||||||
color: colors.tealDark1,
|
color: '#4dbba6',
|
||||||
});
|
});
|
||||||
NumberValue.displayName = 'DataDescription:NumberValue';
|
NumberValue.displayName = 'DataDescription:NumberValue';
|
||||||
|
|
||||||
@@ -57,9 +79,10 @@ const ColorBox = styled.span<{color: string}>((props) => ({
|
|||||||
boxShadow: 'inset 0 0 1px rgba(0, 0, 0, 1)',
|
boxShadow: 'inset 0 0 1px rgba(0, 0, 0, 1)',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
height: 12,
|
height: 12,
|
||||||
marginRight: 5,
|
marginRight: 4,
|
||||||
verticalAlign: 'middle',
|
verticalAlign: 'middle',
|
||||||
width: 12,
|
width: 12,
|
||||||
|
borderRadius: 4,
|
||||||
}));
|
}));
|
||||||
ColorBox.displayName = 'DataDescription:ColorBox';
|
ColorBox.displayName = 'DataDescription:ColorBox';
|
||||||
|
|
||||||
@@ -185,7 +208,7 @@ class NumberTextEditor extends PureComponent<{
|
|||||||
<Input
|
<Input
|
||||||
key="input"
|
key="input"
|
||||||
{...extraProps}
|
{...extraProps}
|
||||||
compact={true}
|
size="small"
|
||||||
onChange={this.onNumberTextInputChange}
|
onChange={this.onNumberTextInputChange}
|
||||||
onKeyDown={this.onNumberTextInputKeyDown}
|
onKeyDown={this.onNumberTextInputKeyDown}
|
||||||
ref={this.onNumberTextRef}
|
ref={this.onNumberTextRef}
|
||||||
@@ -202,7 +225,7 @@ type DataDescriptionState = {
|
|||||||
value: any;
|
value: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class DataDescription extends PureComponent<
|
export class DataDescription extends PureComponent<
|
||||||
DataDescriptionProps,
|
DataDescriptionProps,
|
||||||
DataDescriptionState
|
DataDescriptionState
|
||||||
> {
|
> {
|
||||||
@@ -315,13 +338,15 @@ class ColorEditor extends PureComponent<{
|
|||||||
colorSet?: Array<string | number>;
|
colorSet?: Array<string | number>;
|
||||||
commit: (opts: DescriptionCommitOptions) => void;
|
commit: (opts: DescriptionCommitOptions) => void;
|
||||||
}> {
|
}> {
|
||||||
onBlur = () => {
|
onBlur = (newVisibility: boolean) => {
|
||||||
|
if (!newVisibility) {
|
||||||
this.props.commit({
|
this.props.commit({
|
||||||
clear: true,
|
clear: true,
|
||||||
keep: false,
|
keep: false,
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
set: true,
|
set: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onChange = ({
|
onChange = ({
|
||||||
@@ -388,16 +413,11 @@ class ColorEditor extends PureComponent<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorPickerDescription>
|
<Popover
|
||||||
<DataDescriptionPreview
|
trigger={'click'}
|
||||||
type="color"
|
onVisibleChange={this.onBlur}
|
||||||
value={this.props.value}
|
content={() =>
|
||||||
extra={this.props.colorSet}
|
this.props.colorSet ? (
|
||||||
editable={false}
|
|
||||||
commit={this.props.commit}
|
|
||||||
/>
|
|
||||||
<Popover onDismiss={this.onBlur}>
|
|
||||||
{this.props.colorSet ? (
|
|
||||||
<CompactPicker
|
<CompactPicker
|
||||||
color={colorInfo}
|
color={colorInfo}
|
||||||
colors={this.props.colorSet
|
colors={this.props.colorSet
|
||||||
@@ -425,23 +445,7 @@ class ColorEditor extends PureComponent<{
|
|||||||
) : (
|
) : (
|
||||||
<SketchPicker
|
<SketchPicker
|
||||||
color={colorInfo}
|
color={colorInfo}
|
||||||
presetColors={[
|
presetColors={presetColors}
|
||||||
colors.blue,
|
|
||||||
colors.green,
|
|
||||||
colors.red,
|
|
||||||
colors.blueGrey,
|
|
||||||
colors.slate,
|
|
||||||
colors.aluminum,
|
|
||||||
colors.seaFoam,
|
|
||||||
colors.teal,
|
|
||||||
colors.lime,
|
|
||||||
colors.lemon,
|
|
||||||
colors.orange,
|
|
||||||
colors.tomato,
|
|
||||||
colors.cherry,
|
|
||||||
colors.pink,
|
|
||||||
colors.grape,
|
|
||||||
]}
|
|
||||||
onChange={(color: {
|
onChange={(color: {
|
||||||
hex: string;
|
hex: string;
|
||||||
hsl: {
|
hsl: {
|
||||||
@@ -458,9 +462,18 @@ class ColorEditor extends PureComponent<{
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
</Popover>
|
}>
|
||||||
|
<ColorPickerDescription>
|
||||||
|
<DataDescriptionPreview
|
||||||
|
type="color"
|
||||||
|
value={this.props.value}
|
||||||
|
extra={this.props.colorSet}
|
||||||
|
editable={false}
|
||||||
|
commit={this.props.commit}
|
||||||
|
/>
|
||||||
</ColorPickerDescription>
|
</ColorPickerDescription>
|
||||||
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,65 +518,6 @@ class DataDescriptionPreview extends PureComponent<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseColor(
|
|
||||||
val: string | number,
|
|
||||||
):
|
|
||||||
| {
|
|
||||||
r: number;
|
|
||||||
g: number;
|
|
||||||
b: number;
|
|
||||||
a: number;
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
| null {
|
|
||||||
if (typeof val === 'number') {
|
|
||||||
const a = ((val >> 24) & 0xff) / 255;
|
|
||||||
const r = (val >> 16) & 0xff;
|
|
||||||
const g = (val >> 8) & 0xff;
|
|
||||||
const b = val & 0xff;
|
|
||||||
return {a, b, g, r};
|
|
||||||
}
|
|
||||||
if (typeof val !== 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (val[0] !== '#') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove leading hash
|
|
||||||
val = val.slice(1);
|
|
||||||
|
|
||||||
// only allow RGB and ARGB hex values
|
|
||||||
if (val.length !== 3 && val.length !== 6 && val.length !== 8) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// split every 2 characters
|
|
||||||
const parts = val.match(/.{1,2}/g);
|
|
||||||
if (!parts) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the alpha value
|
|
||||||
let a = 1;
|
|
||||||
|
|
||||||
// extract alpha if passed AARRGGBB
|
|
||||||
if (val.length === 8) {
|
|
||||||
a = parseInt(parts.shift() || '0', 16) / 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = val.length;
|
|
||||||
const [r, g, b] = parts.map((num) => {
|
|
||||||
if (size === 3) {
|
|
||||||
return parseInt(num + num, 16);
|
|
||||||
} else {
|
|
||||||
return parseInt(num, 16);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {a, b, g, r};
|
|
||||||
}
|
|
||||||
|
|
||||||
type Picker = {
|
type Picker = {
|
||||||
values: Set<string>;
|
values: Set<string>;
|
||||||
selected: string;
|
selected: string;
|
||||||
@@ -575,10 +529,10 @@ class DataDescriptionContainer extends PureComponent<{
|
|||||||
editable: boolean;
|
editable: boolean;
|
||||||
commit: (opts: DescriptionCommitOptions) => void;
|
commit: (opts: DescriptionCommitOptions) => void;
|
||||||
}> {
|
}> {
|
||||||
static contextType = _HighlightContext; // Replace with useHighlighter
|
static contextType = HighlightContext; // Replace with useHighlighter
|
||||||
context!: React.ContextType<typeof _HighlightContext>;
|
context!: React.ContextType<typeof HighlightContext>;
|
||||||
|
|
||||||
onChangeCheckbox = (e: React.ChangeEvent<HTMLInputElement>) => {
|
onChangeCheckbox = (e: CheckboxChangeEvent) => {
|
||||||
this.props.commit({
|
this.props.commit({
|
||||||
clear: true,
|
clear: true,
|
||||||
keep: true,
|
keep: true,
|
||||||
@@ -593,22 +547,24 @@ class DataDescriptionContainer extends PureComponent<{
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'timeline': {
|
case 'timeline': {
|
||||||
return (
|
return null;
|
||||||
<>
|
// TODO:
|
||||||
<TimelineDataDescription
|
// return (
|
||||||
canSetCurrent={editable}
|
// <>
|
||||||
timeline={JSON.parse(val)}
|
// <TimelineDataDescription
|
||||||
onClick={(id) => {
|
// canSetCurrent={editable}
|
||||||
this.props.commit({
|
// timeline={JSON.parse(val)}
|
||||||
value: id,
|
// onClick={(id) => {
|
||||||
keep: true,
|
// this.props.commit({
|
||||||
clear: false,
|
// value: id,
|
||||||
set: true,
|
// keep: true,
|
||||||
});
|
// clear: false,
|
||||||
}}
|
// set: true,
|
||||||
/>
|
// });
|
||||||
</>
|
// }}
|
||||||
);
|
// />
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'number':
|
case 'number':
|
||||||
@@ -660,15 +616,15 @@ class DataDescriptionContainer extends PureComponent<{
|
|||||||
|
|
||||||
case 'picker': {
|
case 'picker': {
|
||||||
const picker: Picker = JSON.parse(val);
|
const picker: Picker = JSON.parse(val);
|
||||||
const options = [...picker.values].reduce((obj, value) => {
|
|
||||||
return {...obj, [value]: value};
|
|
||||||
}, {});
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
disabled={!this.props.editable}
|
disabled={!this.props.editable}
|
||||||
options={options}
|
options={Array.from(picker.values).map((value) => ({
|
||||||
selected={picker.selected}
|
value,
|
||||||
onChangeWithKey={(value: string) =>
|
label: value,
|
||||||
|
}))}
|
||||||
|
value={picker.selected}
|
||||||
|
onChange={(value: string) =>
|
||||||
this.props.commit({
|
this.props.commit({
|
||||||
value,
|
value,
|
||||||
keep: true,
|
keep: true,
|
||||||
@@ -688,12 +644,12 @@ class DataDescriptionContainer extends PureComponent<{
|
|||||||
<>
|
<>
|
||||||
<Link href={val}>{highlighter.render(val)}</Link>
|
<Link href={val}>{highlighter.render(val)}</Link>
|
||||||
{editable && (
|
{editable && (
|
||||||
<Glyph
|
<EditOutlined
|
||||||
name="pencil"
|
style={{
|
||||||
variant="outline"
|
color: theme.disabledColor,
|
||||||
color={colors.light20}
|
cursor: 'pointer',
|
||||||
size={16}
|
marginLeft: 8,
|
||||||
style={{cursor: 'pointer', marginLeft: 8}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -709,8 +665,7 @@ class DataDescriptionContainer extends PureComponent<{
|
|||||||
|
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
return editable ? (
|
return editable ? (
|
||||||
<input
|
<Checkbox
|
||||||
type="checkbox"
|
|
||||||
checked={!!val}
|
checked={!!val}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
onChange={this.onChangeCheckbox}
|
onChange={this.onChangeCheckbox}
|
||||||
67
desktop/flipper-plugin/src/utils/parseColor.tsx
Normal file
67
desktop/flipper-plugin/src/utils/parseColor.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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 function parseColor(
|
||||||
|
val: string | number,
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
r: number;
|
||||||
|
g: number;
|
||||||
|
b: number;
|
||||||
|
a: number;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
| null {
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
const a = ((val >> 24) & 0xff) / 255;
|
||||||
|
const r = (val >> 16) & 0xff;
|
||||||
|
const g = (val >> 8) & 0xff;
|
||||||
|
const b = val & 0xff;
|
||||||
|
return {a, b, g, r};
|
||||||
|
}
|
||||||
|
if (typeof val !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (val[0] !== '#') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove leading hash
|
||||||
|
val = val.slice(1);
|
||||||
|
|
||||||
|
// only allow RGB and ARGB hex values
|
||||||
|
if (val.length !== 3 && val.length !== 6 && val.length !== 8) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// split every 2 characters
|
||||||
|
const parts = val.match(/.{1,2}/g);
|
||||||
|
if (!parts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the alpha value
|
||||||
|
let a = 1;
|
||||||
|
|
||||||
|
// extract alpha if passed AARRGGBB
|
||||||
|
if (val.length === 8) {
|
||||||
|
a = parseInt(parts.shift() || '0', 16) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = val.length;
|
||||||
|
const [r, g, b] = parts.map((num) => {
|
||||||
|
if (size === 3) {
|
||||||
|
return parseInt(num + num, 16);
|
||||||
|
} else {
|
||||||
|
return parseInt(num, 16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {a, b, g, r};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user