flipper-plugin
Version:
Flipper Desktop plugin SDK and components
142 lines • 6.16 kB
JavaScript
/**
* 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
;