Split flipper-plugin package
Summary: flipper-server-companion depends on flipper-plugin. flipper-plugin includes dependencies that run only in a browser. Splitting flipper-plugin into core and browser packages helps to avoid including browser-only dependencies into flipper-server bundle. As a result, bundle size could be cut in half. Subsequently, RSS usage drops as there is twice as less code to process for V8. Note: it currently breaks external flipper-data-source package. It will be restored in subsequent diffs Reviewed By: lblasa Differential Revision: D38658285 fbshipit-source-id: 751b11fa9f3a2d938ce166687b8310ba8b059dee
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2090120cda
commit
97b8b8a1c4
@@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
"flipper-plugin": "0.0.0",
|
"flipper-plugin-core": "0.0.0",
|
||||||
"immer": "^9.0.12",
|
"immer": "^9.0.12",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
"p-map": "^4.0.0",
|
"p-map": "^4.0.0",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
_SandyPluginInstance,
|
_SandyPluginInstance,
|
||||||
getFlipperLib,
|
getFlipperLib,
|
||||||
_SandyPluginDefinition,
|
_SandyPluginDefinition,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin-core';
|
||||||
import {createServerAddOnControls} from './utils/createServerAddOnControls';
|
import {createServerAddOnControls} from './utils/createServerAddOnControls';
|
||||||
import isProduction from './utils/isProduction';
|
import isProduction from './utils/isProduction';
|
||||||
|
|
||||||
@@ -135,17 +135,23 @@ export default abstract class AbstractClient extends EventEmitter {
|
|||||||
initialState?: Record<string, any>,
|
initialState?: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
this.sandyPluginStates.set(
|
const pluginInstance = new _SandyPluginInstance(
|
||||||
plugin.id,
|
this.serverAddOnControls,
|
||||||
new _SandyPluginInstance(
|
getFlipperLib(),
|
||||||
this.serverAddOnControls,
|
plugin,
|
||||||
getFlipperLib(),
|
this,
|
||||||
plugin,
|
getPluginKey(this.id, {serial: this.query.device_id}, plugin.id),
|
||||||
this,
|
initialState,
|
||||||
getPluginKey(this.id, {serial: this.query.device_id}, plugin.id),
|
|
||||||
initialState,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
pluginInstance.events.on('error', (message) => {
|
||||||
|
const error: ClientErrorType = {
|
||||||
|
message,
|
||||||
|
name: 'Plugin Error',
|
||||||
|
stacktrace: '',
|
||||||
|
};
|
||||||
|
this.emit('error', error);
|
||||||
|
});
|
||||||
|
this.sandyPluginStates.set(plugin.id, pluginInstance);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to start plugin '${plugin.id}': `, e);
|
console.error(`Failed to start plugin '${plugin.id}': `, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlipperLib, Notification} from 'flipper-plugin';
|
import {FlipperLib, Notification} from 'flipper-plugin-core';
|
||||||
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
||||||
|
|
||||||
type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action';
|
type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
getLatestCompatibleVersionOfEachPlugin,
|
getLatestCompatibleVersionOfEachPlugin,
|
||||||
} from '../plugins';
|
} from '../plugins';
|
||||||
import {BundledPluginDetails, InstalledPluginDetails} from 'flipper-common';
|
import {BundledPluginDetails, InstalledPluginDetails} from 'flipper-common';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|
||||||
let loadDynamicPluginsMock: jest.Mock;
|
let loadDynamicPluginsMock: jest.Mock;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import BaseDevice from './BaseDevice';
|
import BaseDevice from './BaseDevice';
|
||||||
import type {DeviceOS, DeviceType} from 'flipper-plugin';
|
import type {DeviceOS, DeviceType} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export default class ArchivedDevice extends BaseDevice {
|
export default class ArchivedDevice extends BaseDevice {
|
||||||
isArchived = true;
|
isArchived = true;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
createState,
|
createState,
|
||||||
getFlipperLib,
|
getFlipperLib,
|
||||||
CrashLogListener,
|
CrashLogListener,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin-core';
|
||||||
import {
|
import {
|
||||||
DeviceLogEntry,
|
DeviceLogEntry,
|
||||||
DeviceOS,
|
DeviceOS,
|
||||||
@@ -49,7 +49,11 @@ export default class BaseDevice implements Device {
|
|||||||
hasDevicePlugins = false; // true if there are device plugins for this device (not necessarily enabled)
|
hasDevicePlugins = false; // true if there are device plugins for this device (not necessarily enabled)
|
||||||
private readonly serverAddOnControls: ServerAddOnControls;
|
private readonly serverAddOnControls: ServerAddOnControls;
|
||||||
|
|
||||||
constructor(flipperServer: FlipperServer, description: DeviceDescription) {
|
constructor(
|
||||||
|
flipperServer: FlipperServer,
|
||||||
|
description: DeviceDescription,
|
||||||
|
private pluginErrorHandler?: (msg: string) => void,
|
||||||
|
) {
|
||||||
this.flipperServer = flipperServer;
|
this.flipperServer = flipperServer;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.serverAddOnControls = createServerAddOnControls(this.flipperServer);
|
this.serverAddOnControls = createServerAddOnControls(this.flipperServer);
|
||||||
@@ -341,18 +345,19 @@ export default class BaseDevice implements Device {
|
|||||||
this.hasDevicePlugins = true;
|
this.hasDevicePlugins = true;
|
||||||
if (plugin instanceof _SandyPluginDefinition) {
|
if (plugin instanceof _SandyPluginDefinition) {
|
||||||
try {
|
try {
|
||||||
this.sandyPluginStates.set(
|
const pluginInstance = new _SandyDevicePluginInstance(
|
||||||
plugin.id,
|
this.serverAddOnControls,
|
||||||
new _SandyDevicePluginInstance(
|
getFlipperLib(),
|
||||||
this.serverAddOnControls,
|
plugin,
|
||||||
getFlipperLib(),
|
this,
|
||||||
plugin,
|
// break circular dep, one of those days again...
|
||||||
this,
|
getPluginKey(undefined, {serial: this.serial}, plugin.id),
|
||||||
// break circular dep, one of those days again...
|
initialState,
|
||||||
getPluginKey(undefined, {serial: this.serial}, plugin.id),
|
|
||||||
initialState,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if (this.pluginErrorHandler) {
|
||||||
|
pluginInstance.events.on('error', this.pluginErrorHandler);
|
||||||
|
}
|
||||||
|
this.sandyPluginStates.set(plugin.id, pluginInstance);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to start device plugin '${plugin.id}': `, e);
|
console.error(`Failed to start device plugin '${plugin.id}': `, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {DeviceOS, DeviceType} from 'flipper-plugin';
|
import type {DeviceOS, DeviceType} from 'flipper-plugin-core';
|
||||||
import {DeviceSpec} from 'flipper-common';
|
import {DeviceSpec} from 'flipper-common';
|
||||||
import BaseDevice from './BaseDevice';
|
import BaseDevice from './BaseDevice';
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
TestUtils,
|
TestUtils,
|
||||||
_SandyPluginDefinition,
|
_SandyPluginDefinition,
|
||||||
_setFlipperLibImplementation,
|
_setFlipperLibImplementation,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin-core';
|
||||||
import {default as ArchivedDevice} from '../ArchivedDevice';
|
import {default as ArchivedDevice} from '../ArchivedDevice';
|
||||||
import {TestDevice} from '../TestDevice';
|
import {TestDevice} from '../TestDevice';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {assertNever, DownloadFileUpdate} from 'flipper-common';
|
import {assertNever, DownloadFileUpdate} from 'flipper-common';
|
||||||
import {FlipperLib, DownloadFileResponse} from 'flipper-plugin';
|
import {FlipperLib, DownloadFileResponse} from 'flipper-plugin-core';
|
||||||
import {RenderHost} from '../RenderHost';
|
import {RenderHost} from '../RenderHost';
|
||||||
|
|
||||||
export const downloadFileFactory =
|
export const downloadFileFactory =
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RemoteServerContext, FlipperLib} from 'flipper-plugin';
|
import {RemoteServerContext, FlipperLib} from 'flipper-plugin-core';
|
||||||
import {
|
import {
|
||||||
BufferEncoding,
|
BufferEncoding,
|
||||||
ExecOptions,
|
ExecOptions,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
ConcretePluginDetails,
|
ConcretePluginDetails,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {reportUsage} from 'flipper-common';
|
import {reportUsage} from 'flipper-common';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||||
import isPluginCompatible from './utils/isPluginCompatible';
|
import isPluginCompatible from './utils/isPluginCompatible';
|
||||||
import isPluginVersionMoreRecent from './utils/isPluginVersionMoreRecent';
|
import isPluginVersionMoreRecent from './utils/isPluginVersionMoreRecent';
|
||||||
import {getRenderHostInstance} from './RenderHost';
|
import {getRenderHostInstance} from './RenderHost';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"path": "../flipper-common"
|
"path": "../flipper-common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../flipper-plugin"
|
"path": "../flipper-plugin-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../test-utils"
|
"path": "../test-utils"
|
||||||
|
|||||||
1
desktop/flipper-plugin-core/README.md
Normal file
1
desktop/flipper-plugin-core/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# flipper-plugin-core
|
||||||
40
desktop/flipper-plugin-core/package.json
Normal file
40
desktop/flipper-plugin-core/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "flipper-plugin-core",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Flipper Desktop plugin SDK and components",
|
||||||
|
"repository": "facebook/flipper",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"flipperBundlerEntry": "src",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": "https://github.com/facebook/flipper/issues",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.7",
|
||||||
|
"flipper-common": "0.0.0",
|
||||||
|
"immer": "^9.0.12",
|
||||||
|
"js-base64": "^3.7.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"string-natural-compare": "^3.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "17.0.39",
|
||||||
|
"@types/string-natural-compare": "^3.0.2",
|
||||||
|
"jest-mock-console": "^1.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@testing-library/dom": "^7.26.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"reset": "rimraf lib *.tsbuildinfo",
|
||||||
|
"build": "tsc -b",
|
||||||
|
"prepack": "yarn reset && yarn build"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/**/*"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/facebook/flipper",
|
||||||
|
"keywords": [
|
||||||
|
"Flipper"
|
||||||
|
],
|
||||||
|
"author": "Facebook, Inc"
|
||||||
|
}
|
||||||
111
desktop/flipper-plugin-core/src/index.tsx
Normal file
111
desktop/flipper-plugin-core/src/index.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Dummy exports to support running plugin code in a headless context.
|
||||||
|
// We do not want to bundle real code that is going to be used in a browser context to decrease the bundle size.
|
||||||
|
// Yet some parts of the browser-only code is being evaluated at plugin import, not when it is being rendered.
|
||||||
|
// Expand the list of stubs as needed when we onboard more and more headless plugins
|
||||||
|
export const theme = {};
|
||||||
|
export const styled = () => () => ({});
|
||||||
|
|
||||||
|
export {produce, Draft} from 'immer';
|
||||||
|
|
||||||
|
import * as TestUtilites from './test-utils/test-utils';
|
||||||
|
export const TestUtils = TestUtilites;
|
||||||
|
export {StartPluginOptions as _StartPluginOptions} from './test-utils/test-utils';
|
||||||
|
|
||||||
|
import './plugin/PluginBase';
|
||||||
|
|
||||||
|
export {BasePluginInstance as _BasePluginInstance} from './plugin/PluginBase';
|
||||||
|
export {
|
||||||
|
SandyPluginInstance as _SandyPluginInstance,
|
||||||
|
PluginClient,
|
||||||
|
PluginFactory as _PluginFactory,
|
||||||
|
RealFlipperClient as _RealFlipperClient,
|
||||||
|
} from './plugin/Plugin';
|
||||||
|
export {
|
||||||
|
Device,
|
||||||
|
DeviceLogListener,
|
||||||
|
DevicePluginClient,
|
||||||
|
CrashLogListener,
|
||||||
|
SandyDevicePluginInstance as _SandyDevicePluginInstance,
|
||||||
|
DevicePluginFactory as _DevicePluginFactory,
|
||||||
|
} from './plugin/DevicePlugin';
|
||||||
|
export {
|
||||||
|
SandyPluginDefinition as _SandyPluginDefinition,
|
||||||
|
FlipperPluginInstance,
|
||||||
|
FlipperPluginModule as _FlipperPluginModule,
|
||||||
|
FlipperDevicePluginModule as _FlipperDevicePluginModule,
|
||||||
|
} from './plugin/SandyPluginDefinition';
|
||||||
|
|
||||||
|
export {
|
||||||
|
DataSource,
|
||||||
|
DataSourceView as _DataSourceView,
|
||||||
|
DataSourceOptionKey as _DataSourceOptionKey,
|
||||||
|
DataSourceOptions as _DataSourceOptions,
|
||||||
|
} from './data-source/DataSource';
|
||||||
|
export {createDataSource} from './state/createDataSource';
|
||||||
|
|
||||||
|
export {
|
||||||
|
createState,
|
||||||
|
Atom,
|
||||||
|
isAtom,
|
||||||
|
ReadOnlyAtom as _ReadOnlyAtom,
|
||||||
|
AtomValue as _AtomValue,
|
||||||
|
} from './state/atom';
|
||||||
|
export {
|
||||||
|
setBatchedUpdateImplementation as _setBatchedUpdateImplementation,
|
||||||
|
batch,
|
||||||
|
} from './state/batch';
|
||||||
|
export {
|
||||||
|
FlipperLib,
|
||||||
|
getFlipperLib,
|
||||||
|
setFlipperLibImplementation as _setFlipperLibImplementation,
|
||||||
|
tryGetFlipperLibImplementation as _tryGetFlipperLibImplementation,
|
||||||
|
FileDescriptor,
|
||||||
|
FileEncoding,
|
||||||
|
RemoteServerContext,
|
||||||
|
DownloadFileResponse,
|
||||||
|
} from './plugin/FlipperLib';
|
||||||
|
export {
|
||||||
|
MenuEntry,
|
||||||
|
NormalizedMenuEntry,
|
||||||
|
buildInMenuEntries as _buildInMenuEntries,
|
||||||
|
DefaultKeyboardAction,
|
||||||
|
} from './plugin/MenuEntry';
|
||||||
|
export {Notification} from './plugin/Notification';
|
||||||
|
export {CreatePasteArgs, CreatePasteResult} from './plugin/Paste';
|
||||||
|
|
||||||
|
export {Idler} from './utils/Idler';
|
||||||
|
|
||||||
|
export {
|
||||||
|
makeShallowSerializable as _makeShallowSerializable,
|
||||||
|
deserializeShallowObject as _deserializeShallowObject,
|
||||||
|
} from './utils/shallowSerialization';
|
||||||
|
|
||||||
|
import * as path from './utils/path';
|
||||||
|
export {path};
|
||||||
|
export {safeStringify} from './utils/safeStringify';
|
||||||
|
export {stubLogger as _stubLogger} from './utils/Logger';
|
||||||
|
|
||||||
|
export {
|
||||||
|
sleep,
|
||||||
|
timeout,
|
||||||
|
createControlledPromise,
|
||||||
|
uuid,
|
||||||
|
DeviceOS,
|
||||||
|
DeviceType,
|
||||||
|
DeviceLogEntry,
|
||||||
|
DeviceLogLevel,
|
||||||
|
Logger,
|
||||||
|
CrashLog,
|
||||||
|
ServerAddOn,
|
||||||
|
ServerAddOnPluginConnection,
|
||||||
|
FlipperServerForServerAddOn,
|
||||||
|
} from 'flipper-common';
|
||||||
@@ -7,12 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {ReactElement} from 'react';
|
||||||
import {Logger} from '../utils/Logger';
|
import {Logger} from '../utils/Logger';
|
||||||
import {Device} from './DevicePlugin';
|
import {Device} from './DevicePlugin';
|
||||||
import {NormalizedMenuEntry} from './MenuEntry';
|
import {NormalizedMenuEntry} from './MenuEntry';
|
||||||
import {RealFlipperClient} from './Plugin';
|
import {RealFlipperClient} from './Plugin';
|
||||||
import {Notification} from './Notification';
|
import {Notification} from './Notification';
|
||||||
import {DetailSidebarProps} from '../ui/DetailSidebar';
|
|
||||||
import {
|
import {
|
||||||
ExecOptions,
|
ExecOptions,
|
||||||
ExecOut,
|
ExecOut,
|
||||||
@@ -109,9 +109,11 @@ export interface FlipperLib {
|
|||||||
writeTextToClipboard(text: string): void;
|
writeTextToClipboard(text: string): void;
|
||||||
openLink(url: string): void;
|
openLink(url: string): void;
|
||||||
showNotification(pluginKey: string, notification: Notification): void;
|
showNotification(pluginKey: string, notification: Notification): void;
|
||||||
DetailsSidebarImplementation?(
|
DetailsSidebarImplementation?(props: {
|
||||||
props: DetailSidebarProps,
|
children: any;
|
||||||
): React.ReactElement | null;
|
width?: number;
|
||||||
|
minWidth?: number;
|
||||||
|
}): ReactElement | null;
|
||||||
/**
|
/**
|
||||||
* @returns
|
* @returns
|
||||||
* Imported file data.
|
* Imported file data.
|
||||||
@@ -7,10 +7,11 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
message: string | React.ReactNode;
|
message: string | ReactNode;
|
||||||
severity: 'warning' | 'error';
|
severity: 'warning' | 'error';
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
category?: string;
|
category?: string;
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
EventsContract,
|
EventsContract,
|
||||||
MethodsContract,
|
MethodsContract,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
|
import type {FC} from 'react';
|
||||||
|
|
||||||
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
||||||
[Key in keyof Contract]?: never;
|
[Key in keyof Contract]?: never;
|
||||||
@@ -140,7 +141,7 @@ export type PluginFactory<
|
|||||||
client: PluginClient<Events, Methods, ServerAddOnEvents, ServerAddOnMethods>,
|
client: PluginClient<Events, Methods, ServerAddOnEvents, ServerAddOnMethods>,
|
||||||
) => object;
|
) => object;
|
||||||
|
|
||||||
export type FlipperPluginComponent = React.FC<{}>;
|
export type FlipperPluginComponent = FC<{}>;
|
||||||
|
|
||||||
export class SandyPluginInstance extends BasePluginInstance {
|
export class SandyPluginInstance extends BasePluginInstance {
|
||||||
static is(thing: any): thing is SandyPluginInstance {
|
static is(thing: any): thing is SandyPluginInstance {
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {message} from 'antd';
|
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry';
|
import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry';
|
||||||
@@ -312,7 +311,7 @@ export abstract class BasePluginInstance {
|
|||||||
// msg is already specific
|
// msg is already specific
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error(msg, e);
|
console.error(msg, e);
|
||||||
message.error(msg);
|
this.events.emit('error', msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.initialStates = undefined;
|
this.initialStates = undefined;
|
||||||
@@ -325,7 +324,7 @@ export abstract class BasePluginInstance {
|
|||||||
// msg is already specific
|
// msg is already specific
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error(msg, e);
|
console.error(msg, e);
|
||||||
message.error(msg);
|
this.events.emit('error', msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
141
desktop/flipper-plugin-core/src/state/atom.tsx
Normal file
141
desktop/flipper-plugin-core/src/state/atom.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {produce, Draft, enableMapSet} from 'immer';
|
||||||
|
import {
|
||||||
|
getCurrentPluginInstance,
|
||||||
|
Persistable,
|
||||||
|
registerStorageAtom,
|
||||||
|
} from '../plugin/PluginBase';
|
||||||
|
import {
|
||||||
|
deserializeShallowObject,
|
||||||
|
makeShallowSerializable,
|
||||||
|
} from '../utils/shallowSerialization';
|
||||||
|
|
||||||
|
enableMapSet();
|
||||||
|
|
||||||
|
export interface ReadOnlyAtom<T> {
|
||||||
|
get(): T;
|
||||||
|
subscribe(listener: (value: T, prevValue: T) => void): () => void;
|
||||||
|
unsubscribe(listener: (value: T, prevValue: T) => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Atom<T> extends ReadOnlyAtom<T> {
|
||||||
|
set(newValue: T): void;
|
||||||
|
update(recipe: (draft: Draft<T>) => void): void;
|
||||||
|
update<X extends T>(recipe: (draft: X) => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AtomValue<T> implements Atom<T>, Persistable {
|
||||||
|
value: T;
|
||||||
|
listeners: ((value: T, prevValue: T) => void)[] = [];
|
||||||
|
|
||||||
|
constructor(initialValue: T) {
|
||||||
|
this.value = initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(nextValue: T) {
|
||||||
|
if (nextValue !== this.value) {
|
||||||
|
const prevValue = this.value;
|
||||||
|
this.value = nextValue;
|
||||||
|
this.notifyChanged(prevValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize(value: T) {
|
||||||
|
this.set(deserializeShallowObject(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return makeShallowSerializable(this.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
update(recipe: (draft: Draft<T>) => void) {
|
||||||
|
this.set(produce(this.value, recipe));
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChanged(prevValue: T) {
|
||||||
|
// TODO: add scheduling
|
||||||
|
this.listeners.slice().forEach((l) => l(this.value, prevValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(listener: (value: T, prevValue: T) => void) {
|
||||||
|
this.listeners.push(listener);
|
||||||
|
return () => this.unsubscribe(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(listener: (value: T, prevValue: T) => void) {
|
||||||
|
const idx = this.listeners.indexOf(listener);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.listeners.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateOptions = {
|
||||||
|
/**
|
||||||
|
* Should this state persist when exporting a plugin?
|
||||||
|
* If set, the atom will be saved / loaded under the key provided
|
||||||
|
*/
|
||||||
|
persist?: string;
|
||||||
|
/**
|
||||||
|
* Store this state in local storage, instead of as part of the plugin import / export.
|
||||||
|
* State stored in local storage is shared between the same plugin
|
||||||
|
* across multiple clients/ devices, but not actively synced.
|
||||||
|
*/
|
||||||
|
persistToLocalStorage?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createState<T>(
|
||||||
|
initialValue: T,
|
||||||
|
options?: StateOptions,
|
||||||
|
): Atom<T>;
|
||||||
|
export function createState<T>(): Atom<T | undefined>;
|
||||||
|
export function createState(
|
||||||
|
initialValue: any = undefined,
|
||||||
|
options: StateOptions = {},
|
||||||
|
): Atom<any> {
|
||||||
|
const atom = new AtomValue(initialValue);
|
||||||
|
if (options?.persistToLocalStorage) {
|
||||||
|
syncAtomWithLocalStorage(options, atom);
|
||||||
|
} else {
|
||||||
|
registerStorageAtom(options.persist, atom);
|
||||||
|
}
|
||||||
|
return atom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncAtomWithLocalStorage(options: StateOptions, atom: AtomValue<any>) {
|
||||||
|
if (!options?.persist) {
|
||||||
|
throw new Error(
|
||||||
|
"The 'persist' option should be set when 'persistToLocalStorage' is set",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const pluginInstance = getCurrentPluginInstance();
|
||||||
|
if (!pluginInstance) {
|
||||||
|
throw new Error(
|
||||||
|
"The 'persistToLocalStorage' option cannot be used outside a plugin definition",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const storageKey = `flipper:${pluginInstance.definition.id}:atom:${options.persist}`;
|
||||||
|
const storedValue = window.localStorage.getItem(storageKey);
|
||||||
|
if (storedValue != null) {
|
||||||
|
atom.deserialize(JSON.parse(storedValue));
|
||||||
|
}
|
||||||
|
atom.subscribe(() => {
|
||||||
|
window.localStorage.setItem(storageKey, JSON.stringify(atom.serialize()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAtom(value: any): value is Atom<any> {
|
||||||
|
return value instanceof AtomValue;
|
||||||
|
}
|
||||||
29
desktop/flipper-plugin-core/src/state/batch.tsx
Normal file
29
desktop/flipper-plugin-core/src/state/batch.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export let batch: (callback: (...args: any[]) => void) => void = (callback) =>
|
||||||
|
callback();
|
||||||
|
|
||||||
|
export const setBatchedUpdateImplementation = (
|
||||||
|
impl: (callback: (...args: any[]) => void) => void,
|
||||||
|
) => {
|
||||||
|
batch = impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function batched<T extends Function>(fn: T): T;
|
||||||
|
export function batched(fn: any) {
|
||||||
|
return function (this: any) {
|
||||||
|
let res: any;
|
||||||
|
batch(() => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
res = fn.apply(this, arguments);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
createDataSource as baseCreateDataSource,
|
createDataSource as baseCreateDataSource,
|
||||||
DataSourceOptions as BaseDataSourceOptions,
|
DataSourceOptions as BaseDataSourceOptions,
|
||||||
DataSourceOptionKey as BaseDataSourceOptionKey,
|
DataSourceOptionKey as BaseDataSourceOptionKey,
|
||||||
} from '../data-source/index';
|
} from '../data-source/DataSource';
|
||||||
import {registerStorageAtom} from '../plugin/PluginBase';
|
import {registerStorageAtom} from '../plugin/PluginBase';
|
||||||
|
|
||||||
type DataSourceOptions = BaseDataSourceOptions & {
|
type DataSourceOptions = BaseDataSourceOptions & {
|
||||||
209
desktop/flipper-plugin-core/src/test-utils/test-utils.tsx
Normal file
209
desktop/flipper-plugin-core/src/test-utils/test-utils.tsx
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
BundledPluginDetails,
|
||||||
|
fsConstants,
|
||||||
|
InstalledPluginDetails,
|
||||||
|
} from 'flipper-common';
|
||||||
|
|
||||||
|
import {FlipperServer, FlipperServerCommands} from 'flipper-common';
|
||||||
|
import {Device} from '../plugin/DevicePlugin';
|
||||||
|
import {FlipperLib} from '../plugin/FlipperLib';
|
||||||
|
import {PluginFactory} from '../plugin/Plugin';
|
||||||
|
import {
|
||||||
|
FlipperDevicePluginModule,
|
||||||
|
FlipperPluginModule,
|
||||||
|
SandyPluginDefinition,
|
||||||
|
} from '../plugin/SandyPluginDefinition';
|
||||||
|
import {stubLogger} from '../utils/Logger';
|
||||||
|
|
||||||
|
declare const process: any;
|
||||||
|
|
||||||
|
export interface StartPluginOptions {
|
||||||
|
initialState?: Record<string, any>;
|
||||||
|
isArchived?: boolean;
|
||||||
|
isBackgroundPlugin?: boolean;
|
||||||
|
startUnactivated?: boolean;
|
||||||
|
/** Provide a set of unsupported methods to simulate older clients that don't support certain methods yet */
|
||||||
|
unsupportedMethods?: string[];
|
||||||
|
/**
|
||||||
|
* Provide a set of GKs that are enabled in this test.
|
||||||
|
*/
|
||||||
|
GKs?: string[];
|
||||||
|
testDevice?: Device;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStubFunction(): jest.Mock<any, any> {
|
||||||
|
// we shouldn't be usign jest.fn() outside a unit test, as it would not resolve / cause jest to be bundled up!
|
||||||
|
if (typeof jest !== 'undefined') {
|
||||||
|
return jest.fn();
|
||||||
|
}
|
||||||
|
return (() => {
|
||||||
|
console.warn('Using a stub function outside a test environment!');
|
||||||
|
}) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib {
|
||||||
|
return {
|
||||||
|
isFB: false,
|
||||||
|
logger: stubLogger,
|
||||||
|
enableMenuEntries: createStubFunction(),
|
||||||
|
createPaste: createStubFunction(),
|
||||||
|
GK(gk: string) {
|
||||||
|
return options?.GKs?.includes(gk) || false;
|
||||||
|
},
|
||||||
|
selectPlugin: createStubFunction(),
|
||||||
|
writeTextToClipboard: createStubFunction(),
|
||||||
|
openLink: createStubFunction(),
|
||||||
|
showNotification: createStubFunction(),
|
||||||
|
exportFile: createStubFunction(),
|
||||||
|
importFile: createStubFunction(),
|
||||||
|
paths: {
|
||||||
|
appPath: process.cwd(),
|
||||||
|
homePath: `/dev/null`,
|
||||||
|
staticPath: process.cwd(),
|
||||||
|
tempPath: `/dev/null`,
|
||||||
|
},
|
||||||
|
environmentInfo: {
|
||||||
|
os: {
|
||||||
|
arch: 'Test',
|
||||||
|
unixname: 'test',
|
||||||
|
platform: 'linux',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
intern: {
|
||||||
|
graphGet: createStubFunction(),
|
||||||
|
graphPost: createStubFunction(),
|
||||||
|
},
|
||||||
|
remoteServerContext: {
|
||||||
|
childProcess: {
|
||||||
|
exec: createStubFunction(),
|
||||||
|
},
|
||||||
|
fs: {
|
||||||
|
access: createStubFunction(),
|
||||||
|
pathExists: createStubFunction(),
|
||||||
|
unlink: createStubFunction(),
|
||||||
|
mkdir: createStubFunction(),
|
||||||
|
rm: createStubFunction(),
|
||||||
|
copyFile: createStubFunction(),
|
||||||
|
constants: fsConstants,
|
||||||
|
stat: createStubFunction(),
|
||||||
|
readlink: createStubFunction(),
|
||||||
|
readFile: createStubFunction(),
|
||||||
|
readFileBinary: createStubFunction(),
|
||||||
|
writeFile: createStubFunction(),
|
||||||
|
writeFileBinary: createStubFunction(),
|
||||||
|
},
|
||||||
|
downloadFile: createStubFunction(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMockPluginDetails(
|
||||||
|
details?: Partial<InstalledPluginDetails>,
|
||||||
|
): InstalledPluginDetails {
|
||||||
|
return {
|
||||||
|
id: 'TestPlugin',
|
||||||
|
dir: '',
|
||||||
|
name: 'TestPlugin',
|
||||||
|
specVersion: 0,
|
||||||
|
entry: '',
|
||||||
|
isBundled: false,
|
||||||
|
isActivatable: true,
|
||||||
|
main: '',
|
||||||
|
source: '',
|
||||||
|
title: 'Testing Plugin',
|
||||||
|
version: '',
|
||||||
|
...details,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTestPlugin<T extends PluginFactory<any, any, any, any>>(
|
||||||
|
implementation: Pick<FlipperPluginModule<T>, 'plugin'> &
|
||||||
|
Partial<FlipperPluginModule<T>>,
|
||||||
|
details?: Partial<InstalledPluginDetails>,
|
||||||
|
) {
|
||||||
|
return new SandyPluginDefinition(
|
||||||
|
createMockPluginDetails({
|
||||||
|
pluginType: 'client',
|
||||||
|
...details,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
Component() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
...implementation,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTestDevicePlugin(
|
||||||
|
implementation: Pick<FlipperDevicePluginModule, 'devicePlugin'> &
|
||||||
|
Partial<FlipperDevicePluginModule>,
|
||||||
|
details?: Partial<InstalledPluginDetails>,
|
||||||
|
) {
|
||||||
|
return new SandyPluginDefinition(
|
||||||
|
createMockPluginDetails({
|
||||||
|
pluginType: 'device',
|
||||||
|
...details,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
supportsDevice() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Component() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
...implementation,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMockBundledPluginDetails(
|
||||||
|
details?: Partial<BundledPluginDetails>,
|
||||||
|
): BundledPluginDetails {
|
||||||
|
return {
|
||||||
|
id: 'TestBundledPlugin',
|
||||||
|
name: 'TestBundledPlugin',
|
||||||
|
specVersion: 0,
|
||||||
|
pluginType: 'client',
|
||||||
|
isBundled: true,
|
||||||
|
isActivatable: true,
|
||||||
|
main: '',
|
||||||
|
source: '',
|
||||||
|
title: 'Testing Bundled Plugin',
|
||||||
|
version: '',
|
||||||
|
...details,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFlipperServerMock(
|
||||||
|
overrides?: Partial<FlipperServerCommands>,
|
||||||
|
): FlipperServer {
|
||||||
|
return {
|
||||||
|
async connect() {},
|
||||||
|
on: createStubFunction(),
|
||||||
|
off: createStubFunction(),
|
||||||
|
exec: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(
|
||||||
|
async (cmd: keyof FlipperServerCommands, ...args: any[]) => {
|
||||||
|
if (overrides?.[cmd]) {
|
||||||
|
return (overrides[cmd] as any)(...args);
|
||||||
|
}
|
||||||
|
console.warn(
|
||||||
|
`Empty server response stubbed for command '${cmd}', set 'getRenderHostInstance().flipperServer.exec' in your test to override the behavior.`,
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
close: createStubFunction(),
|
||||||
|
};
|
||||||
|
}
|
||||||
14
desktop/flipper-plugin-core/tsconfig.json
Normal file
14
desktop/flipper-plugin-core/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "lib",
|
||||||
|
"rootDir": "src",
|
||||||
|
"lib": ["dom", "ES2019"],
|
||||||
|
"types": ["jest", "../types/jest-extensions", "react/next", "react-dom/next"]
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../flipper-common"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"@types/react-dom": "^17.0.13",
|
"@types/react-dom": "^17.0.13",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
|
"flipper-plugin-core": "0.0.0",
|
||||||
"immer": "^9.0.12",
|
"immer": "^9.0.12",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {DevicePluginClient, Device} from '../plugin/DevicePlugin';
|
import {DevicePluginClient, Device} from 'flipper-plugin-core';
|
||||||
import {usePlugin} from '../plugin/PluginContext';
|
import {usePlugin} from '../plugin/PluginContext';
|
||||||
import {createState, useValue} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
|
import {useValue} from '../state/atom';
|
||||||
|
|
||||||
export function supportsDevice(_device: Device) {
|
export function supportsDevice(_device: Device) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {PluginClient} from '../plugin/Plugin';
|
import {PluginClient} from 'flipper-plugin-core';
|
||||||
import {usePlugin} from '../plugin/PluginContext';
|
import {usePlugin} from '../plugin/PluginContext';
|
||||||
import {createState, useValue} from '../state/atom';
|
import {useValue} from '../state/atom';
|
||||||
|
import {createState} from 'flipper-plugin-core';
|
||||||
|
|
||||||
type Events = {
|
type Events = {
|
||||||
inc: {
|
inc: {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import * as TestUtils from '../test-utils/test-utils';
|
import * as TestUtils from '../test-utils/test-utils';
|
||||||
import * as testPlugin from './DeviceTestPlugin';
|
import * as testPlugin from './DeviceTestPlugin';
|
||||||
import {createState} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
|
|
||||||
const testLogMessage = {
|
const testLogMessage = {
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
|
|
||||||
import * as TestUtils from '../test-utils/test-utils';
|
import * as TestUtils from '../test-utils/test-utils';
|
||||||
import * as testPlugin from './TestPlugin';
|
import * as testPlugin from './TestPlugin';
|
||||||
import {createState} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
import {PluginClient} from '../plugin/Plugin';
|
import {PluginClient} from 'flipper-plugin-core';
|
||||||
import {DevicePluginClient} from '../plugin/DevicePlugin';
|
import {DevicePluginClient} from 'flipper-plugin-core';
|
||||||
import mockConsole from 'jest-mock-console';
|
import mockConsole from 'jest-mock-console';
|
||||||
import {sleep} from 'flipper-common';
|
import {sleep} from 'flipper-common';
|
||||||
import {createDataSource} from '../state/createDataSource';
|
import {createDataSource} from 'flipper-plugin-core';
|
||||||
|
|
||||||
test('it can start a plugin and lifecycle events', () => {
|
test('it can start a plugin and lifecycle events', () => {
|
||||||
const {instance, ...p} = TestUtils.startPlugin(testPlugin);
|
const {instance, ...p} = TestUtils.startPlugin(testPlugin);
|
||||||
@@ -357,9 +357,6 @@ test('plugins can handle import errors', async () => {
|
|||||||
"An error occurred when importing data for plugin 'TestPlugin': 'Error: Oops",
|
"An error occurred when importing data for plugin 'TestPlugin': 'Error: Oops",
|
||||||
[Error: Oops],
|
[Error: Oops],
|
||||||
],
|
],
|
||||||
Array [
|
|
||||||
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DataSourceView} from './DataSource';
|
// eslint-disable-next-line node/no-extraneous-import
|
||||||
|
import type {_DataSourceView} from 'flipper-plugin-core';
|
||||||
import React, {memo, useCallback, useEffect, useState} from 'react';
|
import React, {memo, useCallback, useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {RedrawContext} from './DataSourceRendererVirtual';
|
import {RedrawContext} from './DataSourceRendererVirtual';
|
||||||
@@ -16,7 +17,7 @@ type DataSourceProps<T extends object, C> = {
|
|||||||
/**
|
/**
|
||||||
* The data view to render
|
* The data view to render
|
||||||
*/
|
*/
|
||||||
dataView: DataSourceView<T, T[keyof T]>;
|
dataView: _DataSourceView<T, T[keyof T]>;
|
||||||
/**
|
/**
|
||||||
* additional context that will be passed verbatim to the itemRenderer, so that it can be easily memoized
|
* additional context that will be passed verbatim to the itemRenderer, so that it can be easily memoized
|
||||||
*/
|
*/
|
||||||
@@ -35,7 +36,7 @@ type DataSourceProps<T extends object, C> = {
|
|||||||
onUpdateAutoScroll?(autoScroll: boolean): void;
|
onUpdateAutoScroll?(autoScroll: boolean): void;
|
||||||
emptyRenderer?:
|
emptyRenderer?:
|
||||||
| null
|
| null
|
||||||
| ((dataView: DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
| ((dataView: _DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import React, {
|
|||||||
useContext,
|
useContext,
|
||||||
createContext,
|
createContext,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {DataSourceView} from './DataSource';
|
// eslint-disable-next-line node/no-extraneous-import
|
||||||
|
import type {_DataSourceView} from 'flipper-plugin-core';
|
||||||
import {useVirtual} from 'react-virtual';
|
import {useVirtual} from 'react-virtual';
|
||||||
import observeRect from '@reach/observe-rect';
|
import observeRect from '@reach/observe-rect';
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ type DataSourceProps<T extends object, C> = {
|
|||||||
/**
|
/**
|
||||||
* The data source to render
|
* The data source to render
|
||||||
*/
|
*/
|
||||||
dataView: DataSourceView<T, T[keyof T]>;
|
dataView: _DataSourceView<T, T[keyof T]>;
|
||||||
/**
|
/**
|
||||||
* Automatically scroll if the user is near the end?
|
* Automatically scroll if the user is near the end?
|
||||||
*/
|
*/
|
||||||
@@ -68,7 +69,7 @@ type DataSourceProps<T extends object, C> = {
|
|||||||
onUpdateAutoScroll?(autoScroll: boolean): void;
|
onUpdateAutoScroll?(autoScroll: boolean): void;
|
||||||
emptyRenderer?:
|
emptyRenderer?:
|
||||||
| null
|
| null
|
||||||
| ((dataView: DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
| ((dataView: _DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ The significant difference to many other solutions is that DataSource doesn't pr
|
|||||||
Instead, it keeps internally a mutable dataset (the records stored themselves are still immutable but can be replaced) to which new entries are added.
|
Instead, it keeps internally a mutable dataset (the records stored themselves are still immutable but can be replaced) to which new entries are added.
|
||||||
However, instead of propagating the dataset to the rendering layer, events are emitted instead.
|
However, instead of propagating the dataset to the rendering layer, events are emitted instead.
|
||||||
|
|
||||||
### DataSourceView
|
### _DataSourceView
|
||||||
|
|
||||||
Conceptually, `DataSourceView` is a materialized view of a `DataSource`.
|
Conceptually, `_DataSourceView` is a materialized view of a `DataSource`.
|
||||||
For visualizations, typically the following transformations need to be applied: filter/search, sorting and windowing.
|
For visualizations, typically the following transformations need to be applied: filter/search, sorting and windowing.
|
||||||
|
|
||||||
Where many libraries applies these transformations as part of the _rendering_, DataSourceView applies these operations directly when updates to the dataset are received.
|
Where many libraries applies these transformations as part of the _rendering_, _DataSourceView applies these operations directly when updates to the dataset are received.
|
||||||
As a result the transformations need to be applied only to the newly arriving data.
|
As a result the transformations need to be applied only to the newly arriving data.
|
||||||
For example, if a new record arrives for a sorted dataset, we will apply a binary inseration sort for the new entry, avoiding the need for a full re-sort of the dataset during Rendering.
|
For example, if a new record arrives for a sorted dataset, we will apply a binary inseration sort for the new entry, avoiding the need for a full re-sort of the dataset during Rendering.
|
||||||
|
|
||||||
@@ -66,11 +66,11 @@ The events will describe how the current view should be updated to reflect the d
|
|||||||
|
|
||||||
### DataSourceRendererVirtual
|
### DataSourceRendererVirtual
|
||||||
|
|
||||||
`DataSourceRendererVirtual` is one of the possible visualizations of a DataSourceView.
|
`DataSourceRendererVirtual` is one of the possible visualizations of a _DataSourceView.
|
||||||
It takes care of subscribing to the events emitted by the `DataSourceView`, and applies them when they are relevant (e.g. within the visible window).
|
It takes care of subscribing to the events emitted by the `_DataSourceView`, and applies them when they are relevant (e.g. within the visible window).
|
||||||
Beyond that, it manages virtualizations (using the `react-virtual` library), so that for example scroll interactions are used to move the window of the`DataSourceView`.
|
Beyond that, it manages virtualizations (using the `react-virtual` library), so that for example scroll interactions are used to move the window of the`_DataSourceView`.
|
||||||
|
|
||||||
Typically this component is used as underlying abstraction for a Table representation.
|
Typically this component is used as underlying abstraction for a Table representation.
|
||||||
|
|
||||||
### DataSourceRendererStatic
|
### DataSourceRendererStatic
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ Project setup:
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
* [ ] **Support multiple DataSourceView's per DataSource**: Currently there is a one view per source limitation because we didn't need more yet.
|
* [ ] **Support multiple _DataSourceView's per DataSource**: Currently there is a one view per source limitation because we didn't need more yet.
|
||||||
* [ ] **Break up operations that process the full data set in smaller tasks**: There are several operations that process the full data set, for example changing the sort / filter criteria. Currently this is done synchronously (and we debounce changing the filter), in the future we will split up the filtering in smaller taks to make it efficient. But we don't have a way to efficiently break down sorting into smaller tasks as using insertion sorting is 20x slower than the native sorting mechanism if the full data set needs to be processed.
|
* [ ] **Break up operations that process the full data set in smaller tasks**: There are several operations that process the full data set, for example changing the sort / filter criteria. Currently this is done synchronously (and we debounce changing the filter), in the future we will split up the filtering in smaller taks to make it efficient. But we don't have a way to efficiently break down sorting into smaller tasks as using insertion sorting is 20x slower than the native sorting mechanism if the full data set needs to be processed.
|
||||||
* [ ] **Add built-in support for downsampling data**
|
* [ ] **Add built-in support for downsampling data**
|
||||||
* [ ] **Leverage React concurrent mode**: Currently there is custom scheduler logic to handle high- and low- (outside window) priority updates. In principle this could probably be achieved through React concurrent mode as well, but ANT.design (which is used in Flipper) doesn't support it yet.
|
* [ ] **Leverage React concurrent mode**: Currently there is custom scheduler logic to handle high- and low- (outside window) priority updates. In principle this could probably be achieved through React concurrent mode as well, but ANT.design (which is used in Flipper) doesn't support it yet.
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
DataSource,
|
DataSource,
|
||||||
DataSourceView,
|
_DataSourceView,
|
||||||
createDataSource,
|
createDataSource,
|
||||||
DataSourceOptions,
|
_DataSourceOptions,
|
||||||
DataSourceOptionKey,
|
_DataSourceOptionKey, // eslint-disable-next-line node/no-extraneous-import
|
||||||
} from './DataSource';
|
} from 'flipper-plugin-core';
|
||||||
export {
|
export {
|
||||||
DataSourceRendererVirtual,
|
DataSourceRendererVirtual,
|
||||||
DataSourceVirtualizer,
|
DataSourceVirtualizer,
|
||||||
|
|||||||
@@ -7,52 +7,22 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {produce, Draft} from 'immer';
|
export * from 'flipper-plugin-core';
|
||||||
|
|
||||||
import styledImport from '@emotion/styled';
|
import styledImport from '@emotion/styled';
|
||||||
export const styled = styledImport;
|
export const styled = styledImport;
|
||||||
|
|
||||||
import './plugin/PluginBase';
|
import './state/batch';
|
||||||
|
|
||||||
|
export {useValue} from './state/atom';
|
||||||
|
|
||||||
import * as TestUtilites from './test-utils/test-utils';
|
import * as TestUtilites from './test-utils/test-utils';
|
||||||
|
|
||||||
export {
|
|
||||||
SandyPluginInstance as _SandyPluginInstance,
|
|
||||||
PluginClient,
|
|
||||||
} from './plugin/Plugin';
|
|
||||||
export {
|
|
||||||
Device,
|
|
||||||
DeviceLogListener,
|
|
||||||
DevicePluginClient,
|
|
||||||
CrashLogListener,
|
|
||||||
SandyDevicePluginInstance as _SandyDevicePluginInstance,
|
|
||||||
} from './plugin/DevicePlugin';
|
|
||||||
export {
|
|
||||||
SandyPluginDefinition as _SandyPluginDefinition,
|
|
||||||
FlipperPluginInstance,
|
|
||||||
} from './plugin/SandyPluginDefinition';
|
|
||||||
export {SandyPluginRenderer as _SandyPluginRenderer} from './plugin/PluginRenderer';
|
export {SandyPluginRenderer as _SandyPluginRenderer} from './plugin/PluginRenderer';
|
||||||
export {
|
export {
|
||||||
SandyPluginContext as _SandyPluginContext,
|
SandyPluginContext as _SandyPluginContext,
|
||||||
usePlugin,
|
usePlugin,
|
||||||
} from './plugin/PluginContext';
|
} from './plugin/PluginContext';
|
||||||
export {createState, useValue, Atom, isAtom} from './state/atom';
|
|
||||||
export {batch} from './state/batch';
|
|
||||||
export {
|
|
||||||
FlipperLib,
|
|
||||||
getFlipperLib,
|
|
||||||
setFlipperLibImplementation as _setFlipperLibImplementation,
|
|
||||||
FileDescriptor,
|
|
||||||
FileEncoding,
|
|
||||||
RemoteServerContext,
|
|
||||||
DownloadFileResponse,
|
|
||||||
} from './plugin/FlipperLib';
|
|
||||||
export {
|
|
||||||
MenuEntry,
|
|
||||||
NormalizedMenuEntry,
|
|
||||||
buildInMenuEntries as _buildInMenuEntries,
|
|
||||||
DefaultKeyboardAction,
|
|
||||||
} from './plugin/MenuEntry';
|
|
||||||
export {Notification} from './plugin/Notification';
|
|
||||||
export {CreatePasteArgs, CreatePasteResult} from './plugin/Paste';
|
|
||||||
|
|
||||||
export {theme} from './ui/theme';
|
export {theme} from './ui/theme';
|
||||||
export {Layout} from './ui/Layout';
|
export {Layout} from './ui/Layout';
|
||||||
@@ -83,12 +53,6 @@ export {DataFormatter} from './ui/DataFormatter';
|
|||||||
|
|
||||||
export {useLogger, _LoggerContext} from './utils/useLogger';
|
export {useLogger, _LoggerContext} from './utils/useLogger';
|
||||||
|
|
||||||
export {Idler} from './utils/Idler';
|
|
||||||
|
|
||||||
// Import from the index file directly, to make sure package.json's main field is skipped.
|
|
||||||
export {DataSource} from './data-source/index';
|
|
||||||
export {createDataSource} from './state/createDataSource';
|
|
||||||
|
|
||||||
export {DataTable, DataTableColumn} from './ui/data-table/DataTable';
|
export {DataTable, DataTableColumn} from './ui/data-table/DataTable';
|
||||||
export {DataTableManager} from './ui/data-table/DataTableManager';
|
export {DataTableManager} from './ui/data-table/DataTableManager';
|
||||||
export {DataList} from './ui/DataList';
|
export {DataList} from './ui/DataList';
|
||||||
@@ -127,36 +91,13 @@ export {
|
|||||||
ElementID,
|
ElementID,
|
||||||
} from './ui/elements-inspector/ElementsInspector';
|
} from './ui/elements-inspector/ElementsInspector';
|
||||||
export {useMemoize} from './utils/useMemoize';
|
export {useMemoize} from './utils/useMemoize';
|
||||||
export {
|
|
||||||
makeShallowSerializable as _makeShallowSerializable,
|
|
||||||
deserializeShallowObject as _deserializeShallowObject,
|
|
||||||
} from './utils/shallowSerialization';
|
|
||||||
|
|
||||||
export {createTablePlugin} from './utils/createTablePlugin';
|
export {createTablePlugin} from './utils/createTablePlugin';
|
||||||
|
|
||||||
export {textContent} from './utils/textContent';
|
export {textContent} from './utils/textContent';
|
||||||
import * as path from './utils/path';
|
|
||||||
export {path};
|
|
||||||
export {safeStringify} from './utils/safeStringify';
|
|
||||||
|
|
||||||
// It's not ideal that this exists in flipper-plugin sources directly,
|
// It's not ideal that this exists in flipper-plugin sources directly,
|
||||||
// but is the least pain for plugin authors.
|
// but is the least pain for plugin authors.
|
||||||
// Probably we should make sure that testing-library doesn't end up in our final Flipper bundle (which packages flipper-plugin)
|
// Probably we should make sure that testing-library doesn't end up in our final Flipper bundle (which packages flipper-plugin)
|
||||||
// T69106962
|
// T69106962
|
||||||
export const TestUtils = TestUtilites;
|
export const TestUtils = TestUtilites;
|
||||||
|
|
||||||
export {
|
|
||||||
sleep,
|
|
||||||
timeout,
|
|
||||||
createControlledPromise,
|
|
||||||
uuid,
|
|
||||||
DeviceOS,
|
|
||||||
DeviceType,
|
|
||||||
DeviceLogEntry,
|
|
||||||
DeviceLogLevel,
|
|
||||||
Logger,
|
|
||||||
CrashLog,
|
|
||||||
ServerAddOn,
|
|
||||||
ServerAddOnPluginConnection,
|
|
||||||
FlipperServerForServerAddOn,
|
|
||||||
} from 'flipper-common';
|
|
||||||
|
|||||||
@@ -8,16 +8,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {createContext, useContext} from 'react';
|
import {createContext, useContext} from 'react';
|
||||||
import {SandyPluginInstance, PluginFactory} from './Plugin';
|
import {
|
||||||
import {SandyDevicePluginInstance, DevicePluginFactory} from './DevicePlugin';
|
_SandyDevicePluginInstance,
|
||||||
|
_DevicePluginFactory,
|
||||||
|
_SandyPluginInstance,
|
||||||
|
_PluginFactory,
|
||||||
|
} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export const SandyPluginContext = createContext<
|
export const SandyPluginContext = createContext<
|
||||||
SandyPluginInstance | SandyDevicePluginInstance | undefined
|
_SandyPluginInstance | _SandyDevicePluginInstance | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
export function usePluginInstance():
|
export function usePluginInstance():
|
||||||
| SandyPluginInstance
|
| _SandyPluginInstance
|
||||||
| SandyDevicePluginInstance {
|
| _SandyDevicePluginInstance {
|
||||||
const pluginInstance = useContext(SandyPluginContext);
|
const pluginInstance = useContext(SandyPluginContext);
|
||||||
if (!pluginInstance) {
|
if (!pluginInstance) {
|
||||||
throw new Error('Sandy Plugin context not available');
|
throw new Error('Sandy Plugin context not available');
|
||||||
@@ -26,14 +30,14 @@ export function usePluginInstance():
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function usePluginInstanceMaybe():
|
export function usePluginInstanceMaybe():
|
||||||
| SandyPluginInstance
|
| _SandyPluginInstance
|
||||||
| SandyDevicePluginInstance
|
| _SandyDevicePluginInstance
|
||||||
| undefined {
|
| undefined {
|
||||||
return useContext(SandyPluginContext);
|
return useContext(SandyPluginContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePlugin<
|
export function usePlugin<
|
||||||
Factory extends PluginFactory<any, any, any, any> | DevicePluginFactory,
|
Factory extends _PluginFactory<any, any, any, any> | _DevicePluginFactory,
|
||||||
>(plugin: Factory): ReturnType<Factory> {
|
>(plugin: Factory): ReturnType<Factory> {
|
||||||
const pluginInstance = usePluginInstance();
|
const pluginInstance = usePluginInstance();
|
||||||
// In principle we don't *need* the plugin, but having it passed it makes sure the
|
// In principle we don't *need* the plugin, but having it passed it makes sure the
|
||||||
|
|||||||
@@ -9,20 +9,22 @@
|
|||||||
|
|
||||||
import React, {memo, useEffect, createElement} from 'react';
|
import React, {memo, useEffect, createElement} from 'react';
|
||||||
import {SandyPluginContext} from './PluginContext';
|
import {SandyPluginContext} from './PluginContext';
|
||||||
import {SandyPluginInstance} from './Plugin';
|
import {
|
||||||
import {SandyDevicePluginInstance} from './DevicePlugin';
|
_SandyPluginInstance,
|
||||||
import {BasePluginInstance} from './PluginBase';
|
_SandyDevicePluginInstance,
|
||||||
|
_BasePluginInstance,
|
||||||
|
} from 'flipper-plugin-core';
|
||||||
import {TrackingScope} from '../ui/Tracked';
|
import {TrackingScope} from '../ui/Tracked';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
plugin: SandyPluginInstance | SandyDevicePluginInstance;
|
plugin: _SandyPluginInstance | _SandyDevicePluginInstance;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to render a Sandy plugin container
|
* Component to render a Sandy plugin container
|
||||||
*/
|
*/
|
||||||
export const SandyPluginRenderer = memo(({plugin}: Props) => {
|
export const SandyPluginRenderer = memo(({plugin}: Props) => {
|
||||||
if (!plugin || !(plugin instanceof BasePluginInstance)) {
|
if (!plugin || !(plugin instanceof _BasePluginInstance)) {
|
||||||
throw new Error('Expected plugin, got ' + plugin);
|
throw new Error('Expected plugin, got ' + plugin);
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Exports for server add-ons
|
|
||||||
|
|
||||||
import * as path from './utils/path';
|
|
||||||
export {path};
|
|
||||||
export {safeStringify} from './utils/safeStringify';
|
|
||||||
|
|
||||||
export {
|
|
||||||
sleep,
|
|
||||||
timeout,
|
|
||||||
createControlledPromise,
|
|
||||||
uuid,
|
|
||||||
ServerAddOn,
|
|
||||||
ServerAddOnPluginConnection,
|
|
||||||
FlipperServerForServerAddOn,
|
|
||||||
} from 'flipper-common';
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createState} from '../atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
import * as TestUtils from '../../test-utils/test-utils';
|
import * as TestUtils from '../../test-utils/test-utils';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -7,143 +7,16 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {produce, Draft, enableMapSet} from 'immer';
|
import {_AtomValue, _ReadOnlyAtom} from 'flipper-plugin-core';
|
||||||
import {useState, useEffect} from 'react';
|
import {useState, useEffect} from 'react';
|
||||||
import {
|
|
||||||
getCurrentPluginInstance,
|
|
||||||
Persistable,
|
|
||||||
registerStorageAtom,
|
|
||||||
} from '../plugin/PluginBase';
|
|
||||||
import {
|
|
||||||
deserializeShallowObject,
|
|
||||||
makeShallowSerializable,
|
|
||||||
} from '../utils/shallowSerialization';
|
|
||||||
|
|
||||||
enableMapSet();
|
export function useValue<T>(atom: _ReadOnlyAtom<T>): T;
|
||||||
|
|
||||||
export interface ReadOnlyAtom<T> {
|
|
||||||
get(): T;
|
|
||||||
subscribe(listener: (value: T, prevValue: T) => void): () => void;
|
|
||||||
unsubscribe(listener: (value: T, prevValue: T) => void): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Atom<T> extends ReadOnlyAtom<T> {
|
|
||||||
set(newValue: T): void;
|
|
||||||
update(recipe: (draft: Draft<T>) => void): void;
|
|
||||||
update<X extends T>(recipe: (draft: X) => void): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AtomValue<T> implements Atom<T>, Persistable {
|
|
||||||
value: T;
|
|
||||||
listeners: ((value: T, prevValue: T) => void)[] = [];
|
|
||||||
|
|
||||||
constructor(initialValue: T) {
|
|
||||||
this.value = initialValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
set(nextValue: T) {
|
|
||||||
if (nextValue !== this.value) {
|
|
||||||
const prevValue = this.value;
|
|
||||||
this.value = nextValue;
|
|
||||||
this.notifyChanged(prevValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize(value: T) {
|
|
||||||
this.set(deserializeShallowObject(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize() {
|
|
||||||
return makeShallowSerializable(this.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
update(recipe: (draft: Draft<T>) => void) {
|
|
||||||
this.set(produce(this.value, recipe));
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyChanged(prevValue: T) {
|
|
||||||
// TODO: add scheduling
|
|
||||||
this.listeners.slice().forEach((l) => l(this.value, prevValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(listener: (value: T, prevValue: T) => void) {
|
|
||||||
this.listeners.push(listener);
|
|
||||||
return () => this.unsubscribe(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe(listener: (value: T, prevValue: T) => void) {
|
|
||||||
const idx = this.listeners.indexOf(listener);
|
|
||||||
if (idx !== -1) {
|
|
||||||
this.listeners.splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type StateOptions = {
|
|
||||||
/**
|
|
||||||
* Should this state persist when exporting a plugin?
|
|
||||||
* If set, the atom will be saved / loaded under the key provided
|
|
||||||
*/
|
|
||||||
persist?: string;
|
|
||||||
/**
|
|
||||||
* Store this state in local storage, instead of as part of the plugin import / export.
|
|
||||||
* State stored in local storage is shared between the same plugin
|
|
||||||
* across multiple clients/ devices, but not actively synced.
|
|
||||||
*/
|
|
||||||
persistToLocalStorage?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createState<T>(
|
|
||||||
initialValue: T,
|
|
||||||
options?: StateOptions,
|
|
||||||
): Atom<T>;
|
|
||||||
export function createState<T>(): Atom<T | undefined>;
|
|
||||||
export function createState(
|
|
||||||
initialValue: any = undefined,
|
|
||||||
options: StateOptions = {},
|
|
||||||
): Atom<any> {
|
|
||||||
const atom = new AtomValue(initialValue);
|
|
||||||
if (options?.persistToLocalStorage) {
|
|
||||||
syncAtomWithLocalStorage(options, atom);
|
|
||||||
} else {
|
|
||||||
registerStorageAtom(options.persist, atom);
|
|
||||||
}
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncAtomWithLocalStorage(options: StateOptions, atom: AtomValue<any>) {
|
|
||||||
if (!options?.persist) {
|
|
||||||
throw new Error(
|
|
||||||
"The 'persist' option should be set when 'persistToLocalStorage' is set",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const pluginInstance = getCurrentPluginInstance();
|
|
||||||
if (!pluginInstance) {
|
|
||||||
throw new Error(
|
|
||||||
"The 'persistToLocalStorage' option cannot be used outside a plugin definition",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const storageKey = `flipper:${pluginInstance.definition.id}:atom:${options.persist}`;
|
|
||||||
const storedValue = window.localStorage.getItem(storageKey);
|
|
||||||
if (storedValue != null) {
|
|
||||||
atom.deserialize(JSON.parse(storedValue));
|
|
||||||
}
|
|
||||||
atom.subscribe(() => {
|
|
||||||
window.localStorage.setItem(storageKey, JSON.stringify(atom.serialize()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useValue<T>(atom: ReadOnlyAtom<T>): T;
|
|
||||||
export function useValue<T>(
|
export function useValue<T>(
|
||||||
atom: ReadOnlyAtom<T> | undefined,
|
atom: _ReadOnlyAtom<T> | undefined,
|
||||||
defaultValue: T,
|
defaultValue: T,
|
||||||
): T;
|
): T;
|
||||||
export function useValue<T>(
|
export function useValue<T>(
|
||||||
atom: ReadOnlyAtom<T> | undefined,
|
atom: _ReadOnlyAtom<T> | undefined,
|
||||||
defaultValue?: T,
|
defaultValue?: T,
|
||||||
): T {
|
): T {
|
||||||
const [localValue, setLocalValue] = useState<T>(
|
const [localValue, setLocalValue] = useState<T>(
|
||||||
@@ -156,14 +29,10 @@ export function useValue<T>(
|
|||||||
// atom might have changed between mounting and effect setup
|
// atom might have changed between mounting and effect setup
|
||||||
// in that case, this will cause a re-render, otherwise not
|
// in that case, this will cause a re-render, otherwise not
|
||||||
setLocalValue(atom.get());
|
setLocalValue(atom.get());
|
||||||
(atom as AtomValue<T>).subscribe(setLocalValue);
|
(atom as _AtomValue<T>).subscribe(setLocalValue);
|
||||||
return () => {
|
return () => {
|
||||||
(atom as AtomValue<T>).unsubscribe(setLocalValue);
|
(atom as _AtomValue<T>).unsubscribe(setLocalValue);
|
||||||
};
|
};
|
||||||
}, [atom]);
|
}, [atom]);
|
||||||
return localValue;
|
return localValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAtom(value: any): value is Atom<any> {
|
|
||||||
return value instanceof AtomValue;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,17 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {unstable_batchedUpdates} from 'react-dom';
|
import {unstable_batchedUpdates} from 'react-dom';
|
||||||
|
import {_setBatchedUpdateImplementation} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export const batch = unstable_batchedUpdates;
|
_setBatchedUpdateImplementation(unstable_batchedUpdates);
|
||||||
|
|
||||||
export function batched<T extends Function>(fn: T): T;
|
|
||||||
export function batched(fn: any) {
|
|
||||||
return function (this: any) {
|
|
||||||
let res: any;
|
|
||||||
batch(() => {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
res = fn.apply(this, arguments);
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,71 +10,45 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type {RenderResult} from '@testing-library/react';
|
import type {RenderResult} from '@testing-library/react';
|
||||||
import {queries} from '@testing-library/dom';
|
import {queries} from '@testing-library/dom';
|
||||||
import {
|
import {ServerAddOnControls} from 'flipper-common';
|
||||||
BundledPluginDetails,
|
|
||||||
fsConstants,
|
|
||||||
InstalledPluginDetails,
|
|
||||||
ServerAddOnControls,
|
|
||||||
} from 'flipper-common';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RealFlipperClient,
|
_RealFlipperClient,
|
||||||
SandyPluginInstance,
|
_SandyPluginInstance,
|
||||||
PluginClient,
|
PluginClient,
|
||||||
PluginFactory,
|
_PluginFactory,
|
||||||
} from '../plugin/Plugin';
|
_SandyPluginDefinition,
|
||||||
import {
|
_FlipperPluginModule,
|
||||||
SandyPluginDefinition,
|
_FlipperDevicePluginModule,
|
||||||
FlipperPluginModule,
|
_SandyDevicePluginInstance,
|
||||||
FlipperDevicePluginModule,
|
|
||||||
} from '../plugin/SandyPluginDefinition';
|
|
||||||
import {SandyPluginRenderer} from '../plugin/PluginRenderer';
|
|
||||||
import {
|
|
||||||
SandyDevicePluginInstance,
|
|
||||||
Device,
|
Device,
|
||||||
DeviceLogListener,
|
DeviceLogListener,
|
||||||
CrashLogListener,
|
CrashLogListener,
|
||||||
} from '../plugin/DevicePlugin';
|
_BasePluginInstance,
|
||||||
import {BasePluginInstance} from '../plugin/PluginBase';
|
FlipperLib,
|
||||||
import {FlipperLib} from '../plugin/FlipperLib';
|
_stubLogger,
|
||||||
import {stubLogger} from '../utils/Logger';
|
Idler,
|
||||||
import {Idler} from '../utils/Idler';
|
createState,
|
||||||
import {createState} from '../state/atom';
|
TestUtils,
|
||||||
import {
|
_StartPluginOptions,
|
||||||
DeviceLogEntry,
|
} from 'flipper-plugin-core';
|
||||||
FlipperServer,
|
import {SandyPluginRenderer} from '../plugin/PluginRenderer';
|
||||||
FlipperServerCommands,
|
import {DeviceLogEntry} from 'flipper-common';
|
||||||
} from 'flipper-common';
|
|
||||||
|
|
||||||
declare const process: any;
|
|
||||||
declare const electronRequire: any;
|
declare const electronRequire: any;
|
||||||
|
|
||||||
type Renderer = RenderResult<typeof queries>;
|
type Renderer = RenderResult<typeof queries>;
|
||||||
|
|
||||||
interface StartPluginOptions {
|
type ExtractClientType<Module extends _FlipperPluginModule<any>> = Parameters<
|
||||||
initialState?: Record<string, any>;
|
|
||||||
isArchived?: boolean;
|
|
||||||
isBackgroundPlugin?: boolean;
|
|
||||||
startUnactivated?: boolean;
|
|
||||||
/** Provide a set of unsupported methods to simulate older clients that don't support certain methods yet */
|
|
||||||
unsupportedMethods?: string[];
|
|
||||||
/**
|
|
||||||
* Provide a set of GKs that are enabled in this test.
|
|
||||||
*/
|
|
||||||
GKs?: string[];
|
|
||||||
testDevice?: Device;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExtractClientType<Module extends FlipperPluginModule<any>> = Parameters<
|
|
||||||
Module['plugin']
|
Module['plugin']
|
||||||
>[0];
|
>[0];
|
||||||
|
|
||||||
type ExtractMethodsType<Module extends FlipperPluginModule<any>> =
|
type ExtractMethodsType<Module extends _FlipperPluginModule<any>> =
|
||||||
ExtractClientType<Module> extends PluginClient<any, infer Methods>
|
ExtractClientType<Module> extends PluginClient<any, infer Methods>
|
||||||
? Methods
|
? Methods
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
type ExtractEventsType<Module extends FlipperPluginModule<any>> =
|
type ExtractEventsType<Module extends _FlipperPluginModule<any>> =
|
||||||
ExtractClientType<Module> extends PluginClient<infer Events, any>
|
ExtractClientType<Module> extends PluginClient<infer Events, any>
|
||||||
? Events
|
? Events
|
||||||
: never;
|
: never;
|
||||||
@@ -125,7 +99,7 @@ interface BasePluginResult {
|
|||||||
serverAddOnControls: ServerAddOnControls;
|
serverAddOnControls: ServerAddOnControls;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StartPluginResult<Module extends FlipperPluginModule<any>>
|
interface StartPluginResult<Module extends _FlipperPluginModule<any>>
|
||||||
extends BasePluginResult {
|
extends BasePluginResult {
|
||||||
/**
|
/**
|
||||||
* the instantiated plugin for this test
|
* the instantiated plugin for this test
|
||||||
@@ -173,7 +147,7 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>>
|
|||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StartDevicePluginResult<Module extends FlipperDevicePluginModule>
|
interface StartDevicePluginResult<Module extends _FlipperDevicePluginModule>
|
||||||
extends BasePluginResult {
|
extends BasePluginResult {
|
||||||
/**
|
/**
|
||||||
* the instantiated plugin for this test
|
* the instantiated plugin for this test
|
||||||
@@ -189,24 +163,14 @@ interface StartDevicePluginResult<Module extends FlipperDevicePluginModule>
|
|||||||
sendLogEntry(logEntry: DeviceLogEntry): void;
|
sendLogEntry(logEntry: DeviceLogEntry): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createStubFunction(): jest.Mock<any, any> {
|
export function startPlugin<Module extends _FlipperPluginModule<any>>(
|
||||||
// we shouldn't be usign jest.fn() outside a unit test, as it would not resolve / cause jest to be bundled up!
|
|
||||||
if (typeof jest !== 'undefined') {
|
|
||||||
return jest.fn();
|
|
||||||
}
|
|
||||||
return (() => {
|
|
||||||
console.warn('Using a stub function outside a test environment!');
|
|
||||||
}) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|
||||||
module: Module,
|
module: Module,
|
||||||
options?: StartPluginOptions,
|
options?: _StartPluginOptions,
|
||||||
): StartPluginResult<Module> {
|
): StartPluginResult<Module> {
|
||||||
const {act} = electronRequire('@testing-library/react');
|
const {act} = electronRequire('@testing-library/react');
|
||||||
|
|
||||||
const definition = new SandyPluginDefinition(
|
const definition = new _SandyPluginDefinition(
|
||||||
createMockPluginDetails(),
|
TestUtils.createMockPluginDetails(),
|
||||||
module,
|
module,
|
||||||
);
|
);
|
||||||
if (definition.isDevicePlugin) {
|
if (definition.isDevicePlugin) {
|
||||||
@@ -215,12 +179,12 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendStub = createStubFunction();
|
const sendStub = TestUtils.createStubFunction();
|
||||||
const flipperUtils = createMockFlipperLib(options);
|
const flipperUtils = TestUtils.createMockFlipperLib(options);
|
||||||
const testDevice = createMockDevice(options);
|
const testDevice = createMockDevice(options);
|
||||||
const appName = 'TestApplication';
|
const appName = 'TestApplication';
|
||||||
const deviceName = 'TestDevice';
|
const deviceName = 'TestDevice';
|
||||||
const fakeFlipperClient: RealFlipperClient = {
|
const fakeFlipperClient: _RealFlipperClient = {
|
||||||
id: `${appName}#${testDevice.os}#${deviceName}#${testDevice.serial}`,
|
id: `${appName}#${testDevice.os}#${deviceName}#${testDevice.serial}`,
|
||||||
plugins: new Set([definition.id]),
|
plugins: new Set([definition.id]),
|
||||||
query: {
|
query: {
|
||||||
@@ -263,7 +227,7 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
|
|
||||||
const serverAddOnControls = createServerAddOnControlsMock();
|
const serverAddOnControls = createServerAddOnControlsMock();
|
||||||
|
|
||||||
const pluginInstance = new SandyPluginInstance(
|
const pluginInstance = new _SandyPluginInstance(
|
||||||
serverAddOnControls,
|
serverAddOnControls,
|
||||||
flipperUtils,
|
flipperUtils,
|
||||||
definition,
|
definition,
|
||||||
@@ -305,9 +269,9 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
export function renderPlugin<Module extends _FlipperPluginModule<any>>(
|
||||||
module: Module,
|
module: Module,
|
||||||
options?: StartPluginOptions,
|
options?: _StartPluginOptions,
|
||||||
): StartPluginResult<Module> & {
|
): StartPluginResult<Module> & {
|
||||||
renderer: Renderer;
|
renderer: Renderer;
|
||||||
act: (cb: () => void) => void;
|
act: (cb: () => void) => void;
|
||||||
@@ -315,7 +279,7 @@ export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
// prevent bundling in UI bundle
|
// prevent bundling in UI bundle
|
||||||
const {render, act} = electronRequire('@testing-library/react');
|
const {render, act} = electronRequire('@testing-library/react');
|
||||||
const res = startPlugin(module, options);
|
const res = startPlugin(module, options);
|
||||||
const pluginInstance: SandyPluginInstance = (res as any)._backingInstance;
|
const pluginInstance: _SandyPluginInstance = (res as any)._backingInstance;
|
||||||
|
|
||||||
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
||||||
|
|
||||||
@@ -330,14 +294,14 @@ export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
export function startDevicePlugin<Module extends _FlipperDevicePluginModule>(
|
||||||
module: Module,
|
module: Module,
|
||||||
options?: StartPluginOptions,
|
options?: _StartPluginOptions,
|
||||||
): StartDevicePluginResult<Module> {
|
): StartDevicePluginResult<Module> {
|
||||||
const {act} = electronRequire('@testing-library/react');
|
const {act} = electronRequire('@testing-library/react');
|
||||||
|
|
||||||
const definition = new SandyPluginDefinition(
|
const definition = new _SandyPluginDefinition(
|
||||||
createMockPluginDetails({pluginType: 'device'}),
|
TestUtils.createMockPluginDetails({pluginType: 'device'}),
|
||||||
module,
|
module,
|
||||||
);
|
);
|
||||||
if (!definition.isDevicePlugin) {
|
if (!definition.isDevicePlugin) {
|
||||||
@@ -346,10 +310,10 @@ export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const flipperLib = createMockFlipperLib(options);
|
const flipperLib = TestUtils.createMockFlipperLib(options);
|
||||||
const testDevice = createMockDevice(options);
|
const testDevice = createMockDevice(options);
|
||||||
const serverAddOnControls = createServerAddOnControlsMock();
|
const serverAddOnControls = createServerAddOnControlsMock();
|
||||||
const pluginInstance = new SandyDevicePluginInstance(
|
const pluginInstance = new _SandyDevicePluginInstance(
|
||||||
serverAddOnControls,
|
serverAddOnControls,
|
||||||
flipperLib,
|
flipperLib,
|
||||||
definition,
|
definition,
|
||||||
@@ -376,9 +340,9 @@ export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
export function renderDevicePlugin<Module extends _FlipperDevicePluginModule>(
|
||||||
module: Module,
|
module: Module,
|
||||||
options?: StartPluginOptions,
|
options?: _StartPluginOptions,
|
||||||
): StartDevicePluginResult<Module> & {
|
): StartDevicePluginResult<Module> & {
|
||||||
renderer: Renderer;
|
renderer: Renderer;
|
||||||
act: (cb: () => void) => void;
|
act: (cb: () => void) => void;
|
||||||
@@ -387,7 +351,7 @@ export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
|
|
||||||
const res = startDevicePlugin(module, options);
|
const res = startDevicePlugin(module, options);
|
||||||
// @ts-ignore hidden api
|
// @ts-ignore hidden api
|
||||||
const pluginInstance: SandyDevicePluginInstance = (res as any)
|
const pluginInstance: _SandyDevicePluginInstance = (res as any)
|
||||||
._backingInstance;
|
._backingInstance;
|
||||||
|
|
||||||
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
||||||
@@ -403,64 +367,8 @@ export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib {
|
|
||||||
return {
|
|
||||||
isFB: false,
|
|
||||||
logger: stubLogger,
|
|
||||||
enableMenuEntries: createStubFunction(),
|
|
||||||
createPaste: createStubFunction(),
|
|
||||||
GK(gk: string) {
|
|
||||||
return options?.GKs?.includes(gk) || false;
|
|
||||||
},
|
|
||||||
selectPlugin: createStubFunction(),
|
|
||||||
writeTextToClipboard: createStubFunction(),
|
|
||||||
openLink: createStubFunction(),
|
|
||||||
showNotification: createStubFunction(),
|
|
||||||
exportFile: createStubFunction(),
|
|
||||||
importFile: createStubFunction(),
|
|
||||||
paths: {
|
|
||||||
appPath: process.cwd(),
|
|
||||||
homePath: `/dev/null`,
|
|
||||||
staticPath: process.cwd(),
|
|
||||||
tempPath: `/dev/null`,
|
|
||||||
},
|
|
||||||
environmentInfo: {
|
|
||||||
os: {
|
|
||||||
arch: 'Test',
|
|
||||||
unixname: 'test',
|
|
||||||
platform: 'linux',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
intern: {
|
|
||||||
graphGet: createStubFunction(),
|
|
||||||
graphPost: createStubFunction(),
|
|
||||||
},
|
|
||||||
remoteServerContext: {
|
|
||||||
childProcess: {
|
|
||||||
exec: createStubFunction(),
|
|
||||||
},
|
|
||||||
fs: {
|
|
||||||
access: createStubFunction(),
|
|
||||||
pathExists: createStubFunction(),
|
|
||||||
unlink: createStubFunction(),
|
|
||||||
mkdir: createStubFunction(),
|
|
||||||
rm: createStubFunction(),
|
|
||||||
copyFile: createStubFunction(),
|
|
||||||
constants: fsConstants,
|
|
||||||
stat: createStubFunction(),
|
|
||||||
readlink: createStubFunction(),
|
|
||||||
readFile: createStubFunction(),
|
|
||||||
readFileBinary: createStubFunction(),
|
|
||||||
writeFile: createStubFunction(),
|
|
||||||
writeFileBinary: createStubFunction(),
|
|
||||||
},
|
|
||||||
downloadFile: createStubFunction(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBasePluginResult(
|
function createBasePluginResult(
|
||||||
pluginInstance: BasePluginInstance,
|
pluginInstance: _BasePluginInstance,
|
||||||
serverAddOnControls: ServerAddOnControls,
|
serverAddOnControls: ServerAddOnControls,
|
||||||
): BasePluginResult {
|
): BasePluginResult {
|
||||||
return {
|
return {
|
||||||
@@ -491,85 +399,7 @@ function createBasePluginResult(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMockPluginDetails(
|
function createMockDevice(options?: _StartPluginOptions): Device & {
|
||||||
details?: Partial<InstalledPluginDetails>,
|
|
||||||
): InstalledPluginDetails {
|
|
||||||
return {
|
|
||||||
id: 'TestPlugin',
|
|
||||||
dir: '',
|
|
||||||
name: 'TestPlugin',
|
|
||||||
specVersion: 0,
|
|
||||||
entry: '',
|
|
||||||
isBundled: false,
|
|
||||||
isActivatable: true,
|
|
||||||
main: '',
|
|
||||||
source: '',
|
|
||||||
title: 'Testing Plugin',
|
|
||||||
version: '',
|
|
||||||
...details,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTestPlugin<T extends PluginFactory<any, any, any, any>>(
|
|
||||||
implementation: Pick<FlipperPluginModule<T>, 'plugin'> &
|
|
||||||
Partial<FlipperPluginModule<T>>,
|
|
||||||
details?: Partial<InstalledPluginDetails>,
|
|
||||||
) {
|
|
||||||
return new SandyPluginDefinition(
|
|
||||||
createMockPluginDetails({
|
|
||||||
pluginType: 'client',
|
|
||||||
...details,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
Component() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
...implementation,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTestDevicePlugin(
|
|
||||||
implementation: Pick<FlipperDevicePluginModule, 'devicePlugin'> &
|
|
||||||
Partial<FlipperDevicePluginModule>,
|
|
||||||
details?: Partial<InstalledPluginDetails>,
|
|
||||||
) {
|
|
||||||
return new SandyPluginDefinition(
|
|
||||||
createMockPluginDetails({
|
|
||||||
pluginType: 'device',
|
|
||||||
...details,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
supportsDevice() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
Component() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
...implementation,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createMockBundledPluginDetails(
|
|
||||||
details?: Partial<BundledPluginDetails>,
|
|
||||||
): BundledPluginDetails {
|
|
||||||
return {
|
|
||||||
id: 'TestBundledPlugin',
|
|
||||||
name: 'TestBundledPlugin',
|
|
||||||
specVersion: 0,
|
|
||||||
pluginType: 'client',
|
|
||||||
isBundled: true,
|
|
||||||
isActivatable: true,
|
|
||||||
main: '',
|
|
||||||
source: '',
|
|
||||||
title: 'Testing Bundled Plugin',
|
|
||||||
version: '',
|
|
||||||
...details,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMockDevice(options?: StartPluginOptions): Device & {
|
|
||||||
addLogEntry(entry: DeviceLogEntry): void;
|
addLogEntry(entry: DeviceLogEntry): void;
|
||||||
} {
|
} {
|
||||||
const logListeners: (undefined | DeviceLogListener)[] = [];
|
const logListeners: (undefined | DeviceLogListener)[] = [];
|
||||||
@@ -608,18 +438,18 @@ function createMockDevice(options?: StartPluginOptions): Device & {
|
|||||||
addLogEntry(entry: DeviceLogEntry) {
|
addLogEntry(entry: DeviceLogEntry) {
|
||||||
logListeners.forEach((f) => f?.(entry));
|
logListeners.forEach((f) => f?.(entry));
|
||||||
},
|
},
|
||||||
executeShell: createStubFunction(),
|
executeShell: TestUtils.createStubFunction(),
|
||||||
clearLogs: createStubFunction(),
|
clearLogs: TestUtils.createStubFunction(),
|
||||||
forwardPort: createStubFunction(),
|
forwardPort: TestUtils.createStubFunction(),
|
||||||
get isConnected() {
|
get isConnected() {
|
||||||
return this.connected.get();
|
return this.connected.get();
|
||||||
},
|
},
|
||||||
installApp(_: string) {
|
installApp(_: string) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
navigateToLocation: createStubFunction(),
|
navigateToLocation: TestUtils.createStubFunction(),
|
||||||
screenshot: createStubFunction(),
|
screenshot: TestUtils.createStubFunction(),
|
||||||
sendMetroCommand: createStubFunction(),
|
sendMetroCommand: TestUtils.createStubFunction(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,38 +468,22 @@ function createStubIdler(): Idler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFlipperServerMock(
|
function createServerAddOnControlsMock(): ServerAddOnControls {
|
||||||
overrides?: Partial<FlipperServerCommands>,
|
|
||||||
): FlipperServer {
|
|
||||||
return {
|
return {
|
||||||
async connect() {},
|
start: TestUtils.createStubFunction(),
|
||||||
on: createStubFunction(),
|
stop: TestUtils.createStubFunction(),
|
||||||
off: createStubFunction(),
|
sendMessage: TestUtils.createStubFunction(),
|
||||||
exec: jest
|
receiveMessage: TestUtils.createStubFunction(),
|
||||||
.fn()
|
receiveAnyMessage: TestUtils.createStubFunction(),
|
||||||
.mockImplementation(
|
unsubscribePlugin: TestUtils.createStubFunction(),
|
||||||
async (cmd: keyof FlipperServerCommands, ...args: any[]) => {
|
unsubscribe: TestUtils.createStubFunction(),
|
||||||
if (overrides?.[cmd]) {
|
|
||||||
return (overrides[cmd] as any)(...args);
|
|
||||||
}
|
|
||||||
console.warn(
|
|
||||||
`Empty server response stubbed for command '${cmd}', set 'getRenderHostInstance().flipperServer.exec' in your test to override the behavior.`,
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
close: createStubFunction(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createServerAddOnControlsMock(): ServerAddOnControls {
|
export const createMockFlipperLib = TestUtils.createMockFlipperLib;
|
||||||
return {
|
export const createMockPluginDetails = TestUtils.createMockPluginDetails;
|
||||||
start: createStubFunction(),
|
export const createTestPlugin = TestUtils.createTestPlugin;
|
||||||
stop: createStubFunction(),
|
export const createTestDevicePlugin = TestUtils.createTestDevicePlugin;
|
||||||
sendMessage: createStubFunction(),
|
export const createMockBundledPluginDetails =
|
||||||
receiveMessage: createStubFunction(),
|
TestUtils.createMockBundledPluginDetails;
|
||||||
receiveAnyMessage: createStubFunction(),
|
export const createFlipperServerMock = TestUtils.createFlipperServerMock;
|
||||||
unsubscribePlugin: createStubFunction(),
|
|
||||||
unsubscribe: createStubFunction(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import {
|
|||||||
import {Button, Typography} from 'antd';
|
import {Button, Typography} from 'antd';
|
||||||
import {isPlainObject, pad} from 'lodash';
|
import {isPlainObject, pad} from 'lodash';
|
||||||
import React, {createElement, Fragment, isValidElement, useState} from 'react';
|
import React, {createElement, Fragment, isValidElement, useState} from 'react';
|
||||||
import {tryGetFlipperLibImplementation} from '../plugin/FlipperLib';
|
import {_tryGetFlipperLibImplementation} from 'flipper-plugin-core';
|
||||||
import {safeStringify} from '../utils/safeStringify';
|
import {safeStringify} from 'flipper-plugin-core';
|
||||||
import {urlRegex} from '../utils/urlRegex';
|
import {urlRegex} from '../utils/urlRegex';
|
||||||
import {useTableRedraw} from '../data-source/index';
|
import {useTableRedraw} from '../data-source/index';
|
||||||
import {theme} from './theme';
|
import {theme} from './theme';
|
||||||
@@ -196,7 +196,7 @@ export function TruncateHelper({
|
|||||||
<Button
|
<Button
|
||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
tryGetFlipperLibImplementation()?.writeTextToClipboard(value);
|
_tryGetFlipperLibImplementation()?.writeTextToClipboard(value);
|
||||||
}}
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {theme} from './theme';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import {DataTableManager} from './data-table/DataTableManager';
|
import {DataTableManager} from './data-table/DataTableManager';
|
||||||
import {useAssertStableRef} from '../utils/useAssertStableRef';
|
import {useAssertStableRef} from '../utils/useAssertStableRef';
|
||||||
import {DataSource} from '../data-source';
|
import {DataSource} from 'flipper-plugin-core';
|
||||||
import {useMakeStableCallback} from '../utils/useMakeStableCallback';
|
import {useMakeStableCallback} from '../utils/useMakeStableCallback';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
@@ -95,7 +95,9 @@ export const DataList: (<T extends object>(
|
|||||||
}: DataListProps<T>) {
|
}: DataListProps<T>) {
|
||||||
// if a tableManagerRef is provided, we piggy back on that same ref
|
// if a tableManagerRef is provided, we piggy back on that same ref
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const tableManagerRef = tableProps.tableManagerRef ?? createRef<undefined | DataTableManager<T>>();
|
const tableManagerRef =
|
||||||
|
tableProps.tableManagerRef ??
|
||||||
|
createRef<undefined | DataTableManager<T>>();
|
||||||
|
|
||||||
useAssertStableRef(onRenderItem, 'onRenderItem');
|
useAssertStableRef(onRenderItem, 'onRenderItem');
|
||||||
useAssertStableRef(enableArrow, 'enableArrow');
|
useAssertStableRef(enableArrow, 'enableArrow');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {tryGetFlipperLibImplementation} from '../plugin/FlipperLib';
|
import {_tryGetFlipperLibImplementation} from 'flipper-plugin-core';
|
||||||
import {Layout} from './Layout';
|
import {Layout} from './Layout';
|
||||||
|
|
||||||
export type DetailSidebarProps = {
|
export type DetailSidebarProps = {
|
||||||
@@ -19,7 +19,7 @@ export type DetailSidebarProps = {
|
|||||||
|
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
export function DetailSidebar(props: DetailSidebarProps) {
|
export function DetailSidebar(props: DetailSidebarProps) {
|
||||||
const lib = tryGetFlipperLibImplementation();
|
const lib = _tryGetFlipperLibImplementation();
|
||||||
if (lib?.DetailsSidebarImplementation) {
|
if (lib?.DetailsSidebarImplementation) {
|
||||||
return <lib.DetailsSidebarImplementation {...props} />;
|
return <lib.DetailsSidebarImplementation {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Alert, ButtonProps, Input, Modal, Radio, Space, Typography} from 'antd';
|
import {Alert, ButtonProps, Input, Modal, Radio, Space, Typography} from 'antd';
|
||||||
import {createState, useValue} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
|
import {useValue} from '../state/atom';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {renderReactRoot} from '../utils/renderReactRoot';
|
import {renderReactRoot} from '../utils/renderReactRoot';
|
||||||
import {Layout} from './Layout';
|
import {Layout} from './Layout';
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
FileEncoding,
|
FileEncoding,
|
||||||
FlipperLib,
|
FlipperLib,
|
||||||
getFlipperLib,
|
getFlipperLib,
|
||||||
} from '../plugin/FlipperLib';
|
} from 'flipper-plugin-core';
|
||||||
import {fromUint8Array} from 'js-base64';
|
import {fromUint8Array} from 'js-base64';
|
||||||
import {assertNever} from 'flipper-common';
|
import {assertNever} from 'flipper-common';
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {Button} from 'antd';
|
import {Button} from 'antd';
|
||||||
import {usePluginInstance} from '../plugin/PluginContext';
|
import {usePluginInstance} from '../plugin/PluginContext';
|
||||||
import {Atom, createState, useValue} from '../state/atom';
|
import {Atom, createState} from 'flipper-plugin-core';
|
||||||
import {useAssertStableRef} from '../utils/useAssertStableRef';
|
import {useAssertStableRef} from '../utils/useAssertStableRef';
|
||||||
|
import {useValue} from '../state/atom';
|
||||||
|
|
||||||
type MasterDetailProps<T> = {
|
type MasterDetailProps<T> = {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ import styled from '@emotion/styled';
|
|||||||
import {keyframes} from '@emotion/css';
|
import {keyframes} from '@emotion/css';
|
||||||
import reactElementToJSXString from 'react-element-to-jsx-string';
|
import reactElementToJSXString from 'react-element-to-jsx-string';
|
||||||
import {SandyPluginContext} from '../plugin/PluginContext';
|
import {SandyPluginContext} from '../plugin/PluginContext';
|
||||||
import {createState, useValue} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
import {SandyDevicePluginInstance} from '../plugin/DevicePlugin';
|
import {useValue} from '../state/atom';
|
||||||
|
import {_SandyDevicePluginInstance} from 'flipper-plugin-core';
|
||||||
import {Layout} from './Layout';
|
import {Layout} from './Layout';
|
||||||
import {BulbTwoTone} from '@ant-design/icons';
|
import {BulbTwoTone} from '@ant-design/icons';
|
||||||
import type {TooltipPlacement} from 'antd/lib/tooltip';
|
import type {TooltipPlacement} from 'antd/lib/tooltip';
|
||||||
import {SandyPluginInstance} from '../plugin/Plugin';
|
import {_SandyPluginInstance} from 'flipper-plugin-core';
|
||||||
import {theme} from './theme';
|
import {theme} from './theme';
|
||||||
import {Tracked} from './Tracked';
|
import {Tracked} from './Tracked';
|
||||||
import {sha256} from '../utils/sha256';
|
import {sha256} from '../utils/sha256';
|
||||||
@@ -37,7 +38,7 @@ const storageKey = `FLIPPER_NUX_STATE`;
|
|||||||
|
|
||||||
export async function getNuxKey(
|
export async function getNuxKey(
|
||||||
elem: React.ReactNode,
|
elem: React.ReactNode,
|
||||||
currentPlugin?: SandyPluginInstance | SandyDevicePluginInstance,
|
currentPlugin?: _SandyPluginInstance | _SandyDevicePluginInstance,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const hash = await sha256(reactElementToJSXString(elem));
|
const hash = await sha256(reactElementToJSXString(elem));
|
||||||
return `${currentPlugin?.definition.id ?? 'flipper'}:${hash}`;
|
return `${currentPlugin?.definition.id ?? 'flipper'}:${hash}`;
|
||||||
@@ -59,14 +60,14 @@ export function createNuxManager() {
|
|||||||
return {
|
return {
|
||||||
async markRead(
|
async markRead(
|
||||||
elem: React.ReactNode,
|
elem: React.ReactNode,
|
||||||
currentPlugin?: SandyPluginInstance | SandyDevicePluginInstance,
|
currentPlugin?: _SandyPluginInstance | _SandyDevicePluginInstance,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
readMap[await getNuxKey(elem, currentPlugin)] = true;
|
readMap[await getNuxKey(elem, currentPlugin)] = true;
|
||||||
save();
|
save();
|
||||||
},
|
},
|
||||||
async isRead(
|
async isRead(
|
||||||
elem: React.ReactNode,
|
elem: React.ReactNode,
|
||||||
currentPlugin?: SandyPluginInstance | SandyDevicePluginInstance,
|
currentPlugin?: _SandyPluginInstance | _SandyDevicePluginInstance,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return !!readMap[await getNuxKey(elem, currentPlugin)];
|
return !!readMap[await getNuxKey(elem, currentPlugin)];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import {getSortedKeys} from './utils';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useHighlighter, HighlightManager} from '../Highlight';
|
import {useHighlighter, HighlightManager} from '../Highlight';
|
||||||
import {Dropdown, Menu, Tooltip} from 'antd';
|
import {Dropdown, Menu, Tooltip} from 'antd';
|
||||||
import {tryGetFlipperLibImplementation} from '../../plugin/FlipperLib';
|
import {_tryGetFlipperLibImplementation} from 'flipper-plugin-core';
|
||||||
import {safeStringify} from '../../utils/safeStringify';
|
import {safeStringify} from 'flipper-plugin-core';
|
||||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||||
import {theme} from '../theme';
|
import {theme} from '../theme';
|
||||||
|
|
||||||
@@ -615,7 +615,7 @@ export const DataInspectorNode: React.FC<DataInspectorProps> = memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getContextMenu() {
|
function getContextMenu() {
|
||||||
const lib = tryGetFlipperLibImplementation();
|
const lib = _tryGetFlipperLibImplementation();
|
||||||
const extraItems = additionalContextMenuItems
|
const extraItems = additionalContextMenuItems
|
||||||
? [
|
? [
|
||||||
additionalContextMenuItems(parentPath, value, name),
|
additionalContextMenuItems(parentPath, value, name),
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import {Percentage} from '../../utils/widthUtils';
|
|||||||
import {
|
import {
|
||||||
DataSourceRendererVirtual,
|
DataSourceRendererVirtual,
|
||||||
DataSourceRendererStatic,
|
DataSourceRendererStatic,
|
||||||
DataSource,
|
|
||||||
DataSourceView,
|
|
||||||
DataSourceVirtualizer,
|
DataSourceVirtualizer,
|
||||||
} from '../../data-source/index';
|
} from '../../data-source/index';
|
||||||
import {
|
import {
|
||||||
@@ -53,7 +51,11 @@ import {Formatter} from '../DataFormatter';
|
|||||||
import {usePluginInstanceMaybe} from '../../plugin/PluginContext';
|
import {usePluginInstanceMaybe} from '../../plugin/PluginContext';
|
||||||
import {debounce} from 'lodash';
|
import {debounce} from 'lodash';
|
||||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||||
import {createDataSource} from '../../state/createDataSource';
|
import {
|
||||||
|
createDataSource,
|
||||||
|
DataSource,
|
||||||
|
_DataSourceView,
|
||||||
|
} from 'flipper-plugin-core';
|
||||||
import {HighlightProvider} from '../Highlight';
|
import {HighlightProvider} from '../Highlight';
|
||||||
import {useLatestRef} from '../../utils/useLatestRef';
|
import {useLatestRef} from '../../utils/useLatestRef';
|
||||||
|
|
||||||
@@ -77,7 +79,7 @@ type DataTableBaseProps<T = any> = {
|
|||||||
onContextMenu?: (selection: undefined | T) => React.ReactElement;
|
onContextMenu?: (selection: undefined | T) => React.ReactElement;
|
||||||
onRenderEmpty?:
|
onRenderEmpty?:
|
||||||
| null
|
| null
|
||||||
| ((dataView?: DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
| ((dataView?: _DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ItemRenderer<T> = (
|
export type ItemRenderer<T> = (
|
||||||
@@ -771,7 +773,7 @@ function syncRecordsToDataSource<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createDefaultEmptyRenderer<T>(dataTableManager?: DataTableManager<T>) {
|
function createDefaultEmptyRenderer<T>(dataTableManager?: DataTableManager<T>) {
|
||||||
return (dataView?: DataSourceView<T, T[keyof T]>) => (
|
return (dataView?: _DataSourceView<T, T[keyof T]>) => (
|
||||||
<EmptyTable dataView={dataView} dataManager={dataTableManager} />
|
<EmptyTable dataView={dataView} dataManager={dataTableManager} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -780,7 +782,7 @@ function EmptyTable<T>({
|
|||||||
dataView,
|
dataView,
|
||||||
dataManager,
|
dataManager,
|
||||||
}: {
|
}: {
|
||||||
dataView?: DataSourceView<T, T[keyof T]>;
|
dataView?: _DataSourceView<T, T[keyof T]>;
|
||||||
dataManager?: DataTableManager<T>;
|
dataManager?: DataTableManager<T>;
|
||||||
}) {
|
}) {
|
||||||
const resetFilters = useCallback(() => {
|
const resetFilters = useCallback(() => {
|
||||||
|
|||||||
@@ -10,13 +10,10 @@
|
|||||||
import type {DataTableColumn} from './DataTable';
|
import type {DataTableColumn} from './DataTable';
|
||||||
import {Percentage} from '../../utils/widthUtils';
|
import {Percentage} from '../../utils/widthUtils';
|
||||||
import {MutableRefObject, Reducer} from 'react';
|
import {MutableRefObject, Reducer} from 'react';
|
||||||
import {
|
import {DataSourceVirtualizer} from '../../data-source/index';
|
||||||
DataSource,
|
|
||||||
DataSourceView,
|
|
||||||
DataSourceVirtualizer,
|
|
||||||
} from '../../data-source/index';
|
|
||||||
import produce, {castDraft, immerable, original} from 'immer';
|
import produce, {castDraft, immerable, original} from 'immer';
|
||||||
import {theme} from '../theme';
|
import {theme} from '../theme';
|
||||||
|
import {DataSource, _DataSourceView} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
||||||
export type Sorting<T = any> = {
|
export type Sorting<T = any> = {
|
||||||
@@ -121,7 +118,7 @@ type DataManagerActions<T> =
|
|||||||
|
|
||||||
type DataManagerConfig<T> = {
|
type DataManagerConfig<T> = {
|
||||||
dataSource: DataSource<T, T[keyof T]>;
|
dataSource: DataSource<T, T[keyof T]>;
|
||||||
dataView: DataSourceView<T, T[keyof T]>;
|
dataView: _DataSourceView<T, T[keyof T]>;
|
||||||
defaultColumns: DataTableColumn<T>[];
|
defaultColumns: DataTableColumn<T>[];
|
||||||
scope: string;
|
scope: string;
|
||||||
onSelect: undefined | ((item: T | undefined, items: T[]) => void);
|
onSelect: undefined | ((item: T | undefined, items: T[]) => void);
|
||||||
@@ -299,7 +296,7 @@ export const dataTableManagerReducer = produce<
|
|||||||
}
|
}
|
||||||
case 'setColumnFilterFromSelection': {
|
case 'setColumnFilterFromSelection': {
|
||||||
const items = getSelectedItems(
|
const items = getSelectedItems(
|
||||||
config.dataView as DataSourceView<any, any>,
|
config.dataView as _DataSourceView<any, any>,
|
||||||
draft.selection,
|
draft.selection,
|
||||||
);
|
);
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
@@ -376,7 +373,7 @@ export type DataTableManager<T> = {
|
|||||||
toggleColumnVisibility(column: keyof T): void;
|
toggleColumnVisibility(column: keyof T): void;
|
||||||
sortColumn(column: keyof T, direction?: SortDirection): void;
|
sortColumn(column: keyof T, direction?: SortDirection): void;
|
||||||
setSearchValue(value: string, addToHistory?: boolean): void;
|
setSearchValue(value: string, addToHistory?: boolean): void;
|
||||||
dataView: DataSourceView<T, T[keyof T]>;
|
dataView: _DataSourceView<T, T[keyof T]>;
|
||||||
toggleSearchValue(): void;
|
toggleSearchValue(): void;
|
||||||
toggleHighlightSearch(): void;
|
toggleHighlightSearch(): void;
|
||||||
setSearchHighlightColor(color: string): void;
|
setSearchHighlightColor(color: string): void;
|
||||||
@@ -386,7 +383,7 @@ export type DataTableManager<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function createDataTableManager<T>(
|
export function createDataTableManager<T>(
|
||||||
dataView: DataSourceView<T, T[keyof T]>,
|
dataView: _DataSourceView<T, T[keyof T]>,
|
||||||
dispatch: DataTableDispatch<T>,
|
dispatch: DataTableDispatch<T>,
|
||||||
stateRef: MutableRefObject<DataManagerState<T>>,
|
stateRef: MutableRefObject<DataManagerState<T>>,
|
||||||
): DataTableManager<T> {
|
): DataTableManager<T> {
|
||||||
@@ -535,14 +532,14 @@ function addColumnFilter<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSelectedItem<T>(
|
export function getSelectedItem<T>(
|
||||||
dataView: DataSourceView<T, T[keyof T]>,
|
dataView: _DataSourceView<T, T[keyof T]>,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
): T | undefined {
|
): T | undefined {
|
||||||
return selection.current < 0 ? undefined : dataView.get(selection.current);
|
return selection.current < 0 ? undefined : dataView.get(selection.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSelectedItems<T>(
|
export function getSelectedItems<T>(
|
||||||
dataView: DataSourceView<T, T[keyof T]>,
|
dataView: _DataSourceView<T, T[keyof T]>,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
): T[] {
|
): T[] {
|
||||||
return [...selection.items]
|
return [...selection.items]
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import {
|
|||||||
Selection,
|
Selection,
|
||||||
} from './DataTableManager';
|
} from './DataTableManager';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {tryGetFlipperLibImplementation} from '../../plugin/FlipperLib';
|
import {
|
||||||
|
_tryGetFlipperLibImplementation,
|
||||||
|
_DataSourceView,
|
||||||
|
} from 'flipper-plugin-core';
|
||||||
import {DataTableColumn} from './DataTable';
|
import {DataTableColumn} from './DataTable';
|
||||||
import {toFirstUpper} from '../../utils/toFirstUpper';
|
import {toFirstUpper} from '../../utils/toFirstUpper';
|
||||||
import {DataSourceView} from '../../data-source/index';
|
|
||||||
import {renderColumnValue} from './TableRow';
|
import {renderColumnValue} from './TableRow';
|
||||||
import {textContent} from '../../utils/textContent';
|
import {textContent} from '../../utils/textContent';
|
||||||
import {theme} from '../theme';
|
import {theme} from '../theme';
|
||||||
@@ -31,7 +33,7 @@ const {Item, SubMenu} = Menu;
|
|||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
export function tableContextMenuFactory<T>(
|
export function tableContextMenuFactory<T>(
|
||||||
dataView: DataSourceView<T, T[keyof T]>,
|
dataView: _DataSourceView<T, T[keyof T]>,
|
||||||
dispatch: DataTableDispatch<T>,
|
dispatch: DataTableDispatch<T>,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
highlightSearchSetting: SearchHighlightSetting,
|
highlightSearchSetting: SearchHighlightSetting,
|
||||||
@@ -45,7 +47,7 @@ export function tableContextMenuFactory<T>(
|
|||||||
onContextMenu?: (selection: undefined | T) => React.ReactElement,
|
onContextMenu?: (selection: undefined | T) => React.ReactElement,
|
||||||
sideBySideOption?: React.ReactElement,
|
sideBySideOption?: React.ReactElement,
|
||||||
) {
|
) {
|
||||||
const lib = tryGetFlipperLibImplementation();
|
const lib = _tryGetFlipperLibImplementation();
|
||||||
if (!lib) {
|
if (!lib) {
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import {DataTable, DataTableColumn} from '../DataTable';
|
import {DataTable, DataTableColumn} from '../DataTable';
|
||||||
import {render, act} from '@testing-library/react';
|
import {render, act} from '@testing-library/react';
|
||||||
import {createDataSource} from '../../../state/createDataSource';
|
import {createDataSource} from 'flipper-plugin-core';
|
||||||
import {computeDataTableFilter, DataTableManager} from '../DataTableManager';
|
import {computeDataTableFilter, DataTableManager} from '../DataTableManager';
|
||||||
import {Button} from 'antd';
|
import {Button} from 'antd';
|
||||||
import {sleep} from 'flipper-common';
|
import {sleep} from 'flipper-common';
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import {theme} from '../theme';
|
|||||||
import {Layout} from '../Layout';
|
import {Layout} from '../Layout';
|
||||||
import {
|
import {
|
||||||
getFlipperLib,
|
getFlipperLib,
|
||||||
tryGetFlipperLibImplementation,
|
_tryGetFlipperLibImplementation,
|
||||||
} from '../../plugin/FlipperLib';
|
} from 'flipper-plugin-core';
|
||||||
import {DownOutlined, RightOutlined} from '@ant-design/icons';
|
import {DownOutlined, RightOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
@@ -533,7 +533,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
isDarwin =
|
isDarwin =
|
||||||
tryGetFlipperLibImplementation()?.environmentInfo.os.platform === 'darwin';
|
_tryGetFlipperLibImplementation()?.environmentInfo.os.platform === 'darwin';
|
||||||
|
|
||||||
onKeyDown = (e: KeyboardEvent<any>) => {
|
onKeyDown = (e: KeyboardEvent<any>) => {
|
||||||
const {selected} = this.props;
|
const {selected} = this.props;
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {notification, Typography} from 'antd';
|
import {notification, Typography} from 'antd';
|
||||||
import {DataSource} from '../data-source/index';
|
import {DataSource} from 'flipper-plugin-core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {PluginClient} from '../plugin/Plugin';
|
import {PluginClient} from 'flipper-plugin-core';
|
||||||
import {usePlugin} from '../plugin/PluginContext';
|
import {usePlugin} from '../plugin/PluginContext';
|
||||||
import {createState} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
import {DataTableColumn} from '../ui/data-table/DataTable';
|
import {DataTableColumn} from '../ui/data-table/DataTable';
|
||||||
import {MasterDetail} from '../ui/MasterDetail';
|
import {MasterDetail} from '../ui/MasterDetail';
|
||||||
import {createDataSource} from '../state/createDataSource';
|
import {createDataSource} from 'flipper-plugin-core';
|
||||||
|
|
||||||
type PluginResult<Raw, Row> = {
|
type PluginResult<Raw, Row> = {
|
||||||
plugin(client: PluginClient<Record<string, Raw | {}>>): {
|
plugin(client: PluginClient<Record<string, Raw | {}>>): {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createState, useValue} from '../state/atom';
|
import {createState} from 'flipper-plugin-core';
|
||||||
|
import {useValue} from '../state/atom';
|
||||||
import React, {ReactPortal} from 'react';
|
import React, {ReactPortal} from 'react';
|
||||||
import {createPortal, unmountComponentAtNode} from 'react-dom';
|
import {createPortal, unmountComponentAtNode} from 'react-dom';
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
import {Logger} from 'flipper-common';
|
import {Logger} from 'flipper-common';
|
||||||
import {createContext, useContext} from 'react';
|
import {createContext, useContext} from 'react';
|
||||||
import {stubLogger} from './Logger';
|
import {_stubLogger} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export const _LoggerContext = createContext<Logger>(stubLogger);
|
export const _LoggerContext = createContext<Logger>(_stubLogger);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the default logger that can be used for console logging,
|
* Provides the default logger that can be used for console logging,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../flipper-common"
|
"path": "../flipper-common"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../flipper-plugin-core"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
"flipper-frontend-core": "0.0.0",
|
"flipper-frontend-core": "0.0.0",
|
||||||
"flipper-plugin": "0.0.0",
|
"flipper-plugin-core": "0.0.0",
|
||||||
"immer": "^9.0.12"
|
"immer": "^9.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
ClientConnection,
|
ClientConnection,
|
||||||
BaseDevice,
|
BaseDevice,
|
||||||
} from 'flipper-frontend-core';
|
} from 'flipper-frontend-core';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export class HeadlessClient extends AbstractClient {
|
export class HeadlessClient extends AbstractClient {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
getRenderHostInstance,
|
getRenderHostInstance,
|
||||||
isSandyPlugin,
|
isSandyPlugin,
|
||||||
} from 'flipper-frontend-core';
|
} from 'flipper-frontend-core';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||||
|
|
||||||
export class HeadlessPluginInitializer extends AbstractPluginInitializer {
|
export class HeadlessPluginInitializer extends AbstractPluginInitializer {
|
||||||
protected async getFlipperVersion() {
|
protected async getFlipperVersion() {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import {
|
|||||||
FlipperCompanionAvailablePlugin,
|
FlipperCompanionAvailablePlugin,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {BaseDevice} from 'flipper-frontend-core';
|
import {BaseDevice} from 'flipper-frontend-core';
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||||
import {isAtom} from 'flipper-plugin';
|
import {isAtom} from 'flipper-plugin-core';
|
||||||
import {HeadlessClient} from './HeadlessClient';
|
import {HeadlessClient} from './HeadlessClient';
|
||||||
import {FlipperServerCompanionEnv} from './init';
|
import {FlipperServerCompanionEnv} from './init';
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {FlipperServer, getLogger} from 'flipper-common';
|
import {FlipperServer, getLogger} from 'flipper-common';
|
||||||
import {getRenderHostInstance, setGlobalObject} from 'flipper-frontend-core';
|
import {getRenderHostInstance, setGlobalObject} from 'flipper-frontend-core';
|
||||||
import * as FlipperPluginSDK from 'flipper-plugin';
|
import * as FlipperPluginSDK from 'flipper-plugin-core';
|
||||||
import * as Immer from 'immer';
|
import * as Immer from 'immer';
|
||||||
import {HeadlessPluginInitializer} from './HeadlessPluginInitializer';
|
import {HeadlessPluginInitializer} from './HeadlessPluginInitializer';
|
||||||
import {initializeFlipperLibImplementation} from './initializeFlipperLibImplementation';
|
import {initializeFlipperLibImplementation} from './initializeFlipperLibImplementation';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Logger, _setFlipperLibImplementation} from 'flipper-plugin';
|
import {Logger, _setFlipperLibImplementation} from 'flipper-plugin-core';
|
||||||
import {baseFlipperLibImplementation, RenderHost} from 'flipper-frontend-core';
|
import {baseFlipperLibImplementation, RenderHost} from 'flipper-frontend-core';
|
||||||
|
|
||||||
export function initializeFlipperLibImplementation(
|
export function initializeFlipperLibImplementation(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"path": "../flipper-frontend-core"
|
"path": "../flipper-frontend-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../flipper-plugin"
|
"path": "../flipper-plugin-core"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
"flipper-doctor": "0.0.0",
|
"flipper-doctor": "0.0.0",
|
||||||
"flipper-plugin": "0.0.0",
|
"flipper-plugin-core": "0.0.0",
|
||||||
"flipper-plugin-lib": "0.0.0",
|
"flipper-plugin-lib": "0.0.0",
|
||||||
"flipper-server-companion": "0.0.0",
|
"flipper-server-companion": "0.0.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DeviceLogEntry} from 'flipper-plugin';
|
import {DeviceLogEntry} from 'flipper-plugin-core';
|
||||||
import {parseAndroidCrash, shouldParseAndroidLog} from '../AndroidCrashUtils';
|
import {parseAndroidCrash, shouldParseAndroidLog} from '../AndroidCrashUtils';
|
||||||
|
|
||||||
function getAndroidLog(
|
function getAndroidLog(
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import {
|
|||||||
ServerAddOnStartDetails,
|
ServerAddOnStartDetails,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {assertNotNull} from '../comms/Utilities';
|
import {assertNotNull} from '../comms/Utilities';
|
||||||
// Special subset of flipper-plugin exports designed for server-side usage
|
import * as FlipperPluginSDK from 'flipper-plugin-core';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import * as FlipperPluginSDK from 'flipper-plugin/src/server';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"path": "../flipper-common"
|
"path": "../flipper-common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../flipper-plugin"
|
"path": "../flipper-plugin-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../flipper-server-companion"
|
"path": "../flipper-server-companion"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {homedir} from 'os';
|
|||||||
const uiSourceDirs = [
|
const uiSourceDirs = [
|
||||||
'flipper-ui-browser',
|
'flipper-ui-browser',
|
||||||
'flipper-ui-core',
|
'flipper-ui-core',
|
||||||
'flipper-plugin',
|
'flipper-plugin-core',
|
||||||
'flipper-common',
|
'flipper-common',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,7 @@
|
|||||||
"flipper-common",
|
"flipper-common",
|
||||||
"flipper-frontend-core",
|
"flipper-frontend-core",
|
||||||
"flipper-plugin",
|
"flipper-plugin",
|
||||||
|
"flipper-plugin-core",
|
||||||
"flipper-server-companion",
|
"flipper-server-companion",
|
||||||
"flipper-server-core",
|
"flipper-server-core",
|
||||||
"flipper-ui-core",
|
"flipper-ui-core",
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../flipper-plugin"
|
"path": "../flipper-plugin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../flipper-plugin-core"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../flipper-server"
|
"path": "../flipper-server"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user