flipper-plugin
Version:
Flipper Desktop plugin SDK and components
473 lines • 20.1 kB
JavaScript
"use strict";
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataDescription = exports.BooleanValue = exports.NumberValue = exports.StringValue = exports.NullValue = exports.presetColors = void 0;
const antd_1 = require("antd");
const react_1 = require("react");
const styled_1 = __importDefault(require("@emotion/styled"));
const react_color_1 = require("react-color");
const react_2 = __importDefault(require("react"));
const Highlight_1 = require("../Highlight");
const parseColor_1 = require("../../utils/parseColor");
const TimelineDataDescription_1 = require("./TimelineDataDescription");
const theme_1 = require("../theme");
const icons_1 = require("@ant-design/icons");
const { Link } = antd_1.Typography;
// Based on FIG UI Core, TODO: does that still makes sense?
exports.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
blueGray: '#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
});
exports.NullValue = styled_1.default.span({
color: theme_1.theme.semanticColors.nullValue,
});
exports.NullValue.displayName = 'DataDescription:NullValue';
const UndefinedValue = styled_1.default.span({
color: theme_1.theme.semanticColors.nullValue,
});
UndefinedValue.displayName = 'DataDescription:UndefinedValue';
exports.StringValue = styled_1.default.span({
color: theme_1.theme.semanticColors.stringValue,
wordWrap: 'break-word',
});
exports.StringValue.displayName = 'DataDescription:StringValue';
const ColorValue = styled_1.default.span({
color: theme_1.theme.semanticColors.colorValue,
});
ColorValue.displayName = 'DataDescription:ColorValue';
const SymbolValue = styled_1.default.span({
color: theme_1.theme.semanticColors.stringValue,
});
SymbolValue.displayName = 'DataDescription:SymbolValue';
exports.NumberValue = styled_1.default.span({
color: theme_1.theme.semanticColors.numberValue,
});
exports.NumberValue.displayName = 'DataDescription:NumberValue';
exports.BooleanValue = styled_1.default.span({
color: theme_1.theme.semanticColors.booleanValue,
});
exports.BooleanValue.displayName = 'DataDescription:BooleanValue';
const ColorBox = styled_1.default.span((props) => ({
backgroundColor: props.color,
boxShadow: `inset 0 0 1px ${theme_1.theme.black}`,
display: 'inline-block',
height: 12,
marginRight: 4,
verticalAlign: 'middle',
width: 12,
borderRadius: 4,
}));
ColorBox.displayName = 'DataDescription:ColorBox';
const FunctionKeyword = styled_1.default.span({
color: theme_1.theme.semanticColors.nullValue,
fontStyle: 'italic',
});
FunctionKeyword.displayName = 'DataDescription:FunctionKeyword';
const FunctionName = styled_1.default.span({
fontStyle: 'italic',
});
FunctionName.displayName = 'DataDescription:FunctionName';
const ColorPickerDescription = styled_1.default.div({
display: 'inline',
position: 'relative',
});
ColorPickerDescription.displayName = 'DataDescription:ColorPickerDescription';
const EmptyObjectValue = styled_1.default.span({
fontStyle: 'italic',
});
EmptyObjectValue.displayName = 'DataDescription:EmptyObjectValue';
class NumberTextEditor extends react_1.PureComponent {
constructor() {
super(...arguments);
this.onNumberTextInputChange = (e) => {
const val = this.props.type === 'number'
? parseFloat(e.target.value)
: e.target.value;
this.props.commit({
clear: false,
keep: true,
value: val,
set: false,
});
};
this.onNumberTextInputKeyDown = (e) => {
if (e.key === 'Enter') {
const val = this.props.type === 'number'
? parseFloat(this.props.value)
: this.props.value;
this.props.commit({ clear: true, keep: true, value: val, set: true });
}
else if (e.key === 'Escape') {
this.props.commit({
clear: true,
keep: false,
value: this.props.origValue,
set: false,
});
}
};
this.onNumberTextRef = (ref) => {
if (ref) {
ref.focus();
}
};
this.onNumberTextBlur = () => {
this.props.commit({
clear: true,
keep: true,
value: this.props.value,
set: true,
});
};
}
render() {
const extraProps = {};
if (this.props.type === 'number') {
// render as a HTML number input
extraProps.type = 'number';
// step="any" allows any sort of float to be input, otherwise we're limited
// to decimal
extraProps.step = 'any';
}
return (react_2.default.createElement(antd_1.Input, { key: "input", ...extraProps, size: "small", onChange: this.onNumberTextInputChange, onKeyDown: this.onNumberTextInputKeyDown, ref: this.onNumberTextRef, onBlur: this.onNumberTextBlur, value: this.props.value, style: { fontSize: 11 } }));
}
}
class DataDescription extends react_1.PureComponent {
constructor(props, context) {
super(props, context);
this.commit = (opts) => {
const { path, setValue } = this.props;
if (opts.keep && setValue && path) {
const val = opts.value;
this.setState({ value: val });
if (opts.set) {
setValue(path, val);
}
}
if (opts.clear) {
this.setState({
editing: false,
origValue: '',
value: '',
});
}
};
this.onEditStart = () => {
this.setState({
editing: this._hasEditUI(),
origValue: this.props.value,
value: this.props.value,
});
};
this.state = {
editing: false,
origValue: '',
value: '',
};
}
_renderEditing() {
const { type, extra } = this.props;
const { origValue, value } = this.state;
if (type === 'string' ||
type === 'text' ||
type === 'number' ||
type === 'enum') {
return (react_2.default.createElement(NumberTextEditor, { type: type, value: value, origValue: origValue, commit: this.commit }));
}
if (type === 'color') {
return react_2.default.createElement(ColorEditor, { value: value, commit: this.commit });
}
if (type === 'color_lite') {
return (react_2.default.createElement(ColorEditor, { value: value, colorSet: extra.colorSet, commit: this.commit }));
}
return null;
}
_hasEditUI() {
const { type } = this.props;
return (type === 'string' ||
type === 'text' ||
type === 'number' ||
type === 'enum' ||
type === 'color' ||
type === 'color_lite');
}
render() {
if (this.state.editing) {
return this._renderEditing();
}
else {
return (react_2.default.createElement(DataDescriptionPreview, { type: this.props.type, value: this.props.value, extra: this.props.extra, editable: !!this.props.setValue, commit: this.commit, onEdit: this.onEditStart }));
}
}
}
exports.DataDescription = DataDescription;
class ColorEditor extends react_1.PureComponent {
constructor() {
super(...arguments);
this.onBlur = (newVisibility) => {
if (!newVisibility) {
this.props.commit({
clear: true,
keep: false,
value: this.props.value,
set: true,
});
}
};
this.onChange = ({ hex, rgb: { a, b, g, r }, }) => {
const prev = this.props.value;
let val;
if (typeof prev === 'string') {
if (a === 1) {
// hex is fine and has an implicit 100% alpha
val = hex;
}
else {
// turn into a css rgba value
val = `rgba(${r}, ${g}, ${b}, ${a})`;
}
}
else if (typeof prev === 'number') {
// compute RRGGBBAA value
val = (Math.round(a * 255) & 0xff) << 24;
val |= (r & 0xff) << 16;
val |= (g & 0xff) << 8;
val |= b & 0xff;
const prevClear = ((prev >> 24) & 0xff) === 0;
const onlyAlphaChanged = (prev & 0x00ffffff) === (val & 0x00ffffff);
if (!onlyAlphaChanged && prevClear) {
val = 0xff000000 | (val & 0x00ffffff);
}
}
else {
return;
}
this.props.commit({ clear: false, keep: true, value: val, set: true });
};
this.onChangeLite = ({ rgb: { a, b, g, r }, }) => {
const prev = this.props.value;
if (typeof prev !== 'number') {
return;
}
// compute RRGGBBAA value
let val = (Math.round(a * 255) & 0xff) << 24;
val |= (r & 0xff) << 16;
val |= (g & 0xff) << 8;
val |= b & 0xff;
this.props.commit({ clear: false, keep: true, value: val, set: true });
};
}
render() {
const colorInfo = (0, parseColor_1.parseColor)(this.props.value);
if (!colorInfo) {
return null;
}
return (react_2.default.createElement(antd_1.Popover, { trigger: 'click', onVisibleChange: this.onBlur, content: () => this.props.colorSet ? (react_2.default.createElement(react_color_1.CompactPicker, { color: colorInfo, colors: this.props.colorSet
.filter((x) => x != 0)
.map(parseColor_1.parseColor)
.map((rgba) => {
if (!rgba) {
return '';
}
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`;
}), onChange: (color) => {
this.onChangeLite({ rgb: { ...color.rgb, a: color.rgb.a || 0 } });
} })) : (react_2.default.createElement(react_color_1.SketchPicker, { color: colorInfo, presetColors: exports.presetColors, onChange: (color) => {
this.onChange({
hex: color.hex,
rgb: { ...color.rgb, a: color.rgb.a || 1 },
});
} })) },
react_2.default.createElement(ColorPickerDescription, null,
react_2.default.createElement(DataDescriptionPreview, { type: "color", value: this.props.value, extra: this.props.colorSet, editable: false, commit: this.props.commit }))));
}
}
class DataDescriptionPreview extends react_1.PureComponent {
constructor() {
super(...arguments);
this.onClick = () => {
const { onEdit } = this.props;
if (this.props.editable && onEdit) {
onEdit();
}
};
}
render() {
const { type, value } = this.props;
const description = (react_2.default.createElement(DataDescriptionContainer, { type: type, value: value, editable: this.props.editable, commit: this.props.commit }));
// booleans are always editable so don't require the onEditStart handler
if (type === 'boolean') {
return description;
}
return (react_2.default.createElement("span", { onClick: this.onClick, role: "button", tabIndex: -1 }, description));
}
}
class DataDescriptionContainer extends react_1.PureComponent {
constructor() {
super(...arguments);
this.onChangeCheckbox = (e) => {
this.props.commit({
clear: true,
keep: true,
value: e.target.checked,
set: true,
});
};
}
render() {
const { type, editable, value: val } = this.props;
const highlighter = this.context;
switch (type) {
case 'timeline': {
return (react_2.default.createElement(react_2.default.Fragment, null,
react_2.default.createElement(TimelineDataDescription_1.TimelineDataDescription, { canSetCurrent: editable, timeline: JSON.parse(val), onClick: (id) => {
this.props.commit({
value: id,
keep: true,
clear: false,
set: true,
});
} })));
}
case 'number':
return react_2.default.createElement(exports.NumberValue, null, +val);
case 'bigint':
return react_2.default.createElement(exports.NumberValue, null, val.toString());
case 'color': {
const colorInfo = (0, parseColor_1.parseColor)(val);
if (typeof val === 'number' && val === 0) {
return react_2.default.createElement(UndefinedValue, null, "(not set)");
}
else if (colorInfo) {
const { a, b, g, r } = colorInfo;
return (react_2.default.createElement(react_2.default.Fragment, null,
react_2.default.createElement(ColorBox, { key: "color-box", color: `rgba(${r}, ${g}, ${b}, ${a})` }),
react_2.default.createElement(ColorValue, { key: "value" },
"rgba(",
r,
", ",
g,
", ",
b,
", ",
a === 1 ? '1' : a.toFixed(2),
")")));
}
else {
return react_2.default.createElement("span", null, "Malformed color");
}
}
case 'color_lite': {
const colorInfo = (0, parseColor_1.parseColor)(val);
if (typeof val === 'number' && val === 0) {
return react_2.default.createElement(UndefinedValue, null, "(not set)");
}
else if (colorInfo) {
const { a, b, g, r } = colorInfo;
return (react_2.default.createElement(react_2.default.Fragment, null,
react_2.default.createElement(ColorBox, { key: "color-box", color: `rgba(${r}, ${g}, ${b}, ${a})` }),
react_2.default.createElement(ColorValue, { key: "value" },
"rgba(",
r,
", ",
g,
", ",
b,
", ",
a === 1 ? '1' : a.toFixed(2),
")")));
}
else {
return react_2.default.createElement("span", null, "Malformed color");
}
}
case 'picker': {
const picker = JSON.parse(val);
return (react_2.default.createElement(antd_1.Select, { disabled: !this.props.editable, options: Array.from(picker.values).map((value) => ({
value,
label: value,
})), value: picker.selected, onChange: (value) => this.props.commit({
value,
keep: true,
clear: false,
set: true,
}), size: "small", style: { fontSize: 11 }, dropdownMatchSelectWidth: false }));
}
case 'text':
case 'string':
const isUrl = val.startsWith('http://') || val.startsWith('https://');
if (isUrl) {
return (react_2.default.createElement(react_2.default.Fragment, null,
react_2.default.createElement(Link, { href: val }, highlighter.render(val)),
editable && (react_2.default.createElement(icons_1.EditOutlined, { style: {
color: theme_1.theme.disabledColor,
cursor: 'pointer',
marginLeft: 8,
} }))));
}
else {
return (react_2.default.createElement(exports.StringValue, null, highlighter.render(`"${val || ''}"`)));
}
case 'enum':
return react_2.default.createElement(exports.StringValue, null, highlighter.render(val));
case 'boolean':
return editable ? (react_2.default.createElement(antd_1.Checkbox, { checked: !!val, disabled: !editable, onChange: this.onChangeCheckbox })) : (react_2.default.createElement(exports.BooleanValue, null, `${val}`));
case 'undefined':
return react_2.default.createElement(UndefinedValue, null, "undefined");
case 'date':
if (Object.prototype.toString.call(val) === '[object Date]') {
return react_2.default.createElement("span", null, val.toString());
}
else {
return react_2.default.createElement("span", null, val);
}
case 'null':
return react_2.default.createElement(exports.NullValue, null, "null");
// no description necessary as we'll typically wrap it in [] or {} which
// already denotes the type
case 'array':
return val.length <= 0 ? react_2.default.createElement(EmptyObjectValue, null, "[]") : null;
case 'object':
return Object.keys(val ?? {}).length <= 0 ? (react_2.default.createElement(EmptyObjectValue, null, '{}')) : null;
case 'function':
return (react_2.default.createElement("span", null,
react_2.default.createElement(FunctionKeyword, null, "function"),
react_2.default.createElement(FunctionName, null,
"\u00A0",
val.name,
"()")));
case 'symbol':
return react_2.default.createElement(SymbolValue, null, "Symbol()");
default:
return react_2.default.createElement("span", null,
"Unknown type \"",
type,
"\"");
}
}
}
DataDescriptionContainer.contextType = Highlight_1.HighlightContext; // Replace with useHighlighter
//# sourceMappingURL=DataDescription.js.map