UNPKG

flipper-plugin

Version:

Flipper Desktop plugin SDK and components

142 lines 6.16 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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NUX = exports.NuxManagerContext = exports.createNuxManager = exports.getNuxKey = void 0; const react_1 = __importStar(require("react")); const antd_1 = require("antd"); const styled_1 = __importDefault(require("@emotion/styled")); const react_element_to_jsx_string_1 = __importDefault(require("react-element-to-jsx-string")); const PluginContext_1 = require("../plugin/PluginContext"); const atom_1 = require("../state/atom"); const Layout_1 = require("./Layout"); const icons_1 = require("@ant-design/icons"); const theme_1 = require("./theme"); const Tracked_1 = require("./Tracked"); const sha256_1 = require("../utils/sha256"); const { Text } = antd_1.Typography; const storageKey = `FLIPPER_NUX_STATE`; async function getNuxKey(elem, currentPlugin) { const hash = await (0, sha256_1.sha256)((0, react_element_to_jsx_string_1.default)(elem)); return `${currentPlugin?.definition.id ?? 'flipper'}:${hash}`; } exports.getNuxKey = getNuxKey; function createNuxManager() { const ticker = (0, atom_1.createState)(0); let readMap = JSON.parse(window.localStorage.getItem(storageKey) || '{}'); function save() { // trigger all Nux Elements to re-compute state ticker.set(ticker.get() + 1); window.localStorage.setItem(storageKey, JSON.stringify(readMap, null, 2)); } return { async markRead(elem, currentPlugin) { readMap[await getNuxKey(elem, currentPlugin)] = true; save(); }, async isRead(elem, currentPlugin) { return !!readMap[await getNuxKey(elem, currentPlugin)]; }, resetHints() { readMap = {}; save(); }, ticker, }; } exports.createNuxManager = createNuxManager; const stubManager = { async markRead() { }, async isRead() { return true; }, resetHints() { }, ticker: (0, atom_1.createState)(0), }; exports.NuxManagerContext = (0, react_1.createContext)(stubManager); /** * Creates a New-User-eXperience element; a lightbulb that will show the user new features */ function NUX({ children, title, placement, }) { const manager = (0, react_1.useContext)(exports.NuxManagerContext); const pluginInstance = (0, react_1.useContext)(PluginContext_1.SandyPluginContext); // changing the ticker will force `isRead` to be recomputed const _tick = (0, atom_1.useValue)(manager.ticker); // start with Read = true until proven otherwise, to avoid Nux glitches const [isRead, setIsRead] = (0, react_1.useState)(true); (0, react_1.useEffect)(() => { manager .isRead(title, pluginInstance) .then(setIsRead) .catch((e) => { console.warn('Failed to read NUX status', e); }); }, [manager, title, pluginInstance, _tick]); const dismiss = (0, react_1.useCallback)(() => { manager.markRead(title, pluginInstance); }, [title, manager, pluginInstance]); return (react_1.default.createElement(UnanimatedBadge, { count: isRead ? (0) : (react_1.default.createElement(antd_1.Tooltip, { placement: placement, color: theme_1.theme.backgroundWash, title: react_1.default.createElement(Layout_1.Layout.Container, { center: true, gap: true, pad: true, style: { color: theme_1.theme.textColorPrimary } }, react_1.default.createElement(icons_1.BulbTwoTone, { style: { fontSize: 24 } }), react_1.default.createElement(Text, null, title), react_1.default.createElement(Tracked_1.Tracked, { action: `nux:dismiss:${title.substr(0, 50)}` }, react_1.default.createElement(antd_1.Button, { size: "small", type: "default", onClick: dismiss }, "Dismiss"))) }, react_1.default.createElement(Pulse, null))) }, children)); } exports.NUX = NUX; // We force visibility of the badge to invisible if count has dropped, // otherwise ANT will await animation end, which looks really awkard, see D24918536 const UnanimatedBadge = (0, styled_1.default)(antd_1.Badge)(({ count }) => ({ '.ant-scroll-number-custom-component': { visibility: count === 0 ? 'hidden' : undefined, }, })); const Pulse = (props) => (react_1.default.createElement("div", { ...props, style: { ...props.style, background: 'rgba(64, 128, 255, .5)', width: 12, height: 12, cursor: 'pointer', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', } }, react_1.default.createElement("div", { style: { background: '#3578e5', width: 8, height: 8, borderRadius: '50%', } }))); //# sourceMappingURL=NUX.js.map