UNPKG

flipper-plugin

Version:

Flipper Desktop plugin SDK and components

473 lines 20.1 kB
"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