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": {
|
||||
"eventemitter3": "^4.0.7",
|
||||
"flipper-common": "0.0.0",
|
||||
"flipper-plugin": "0.0.0",
|
||||
"flipper-plugin-core": "0.0.0",
|
||||
"immer": "^9.0.12",
|
||||
"js-base64": "^3.7.2",
|
||||
"p-map": "^4.0.0",
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
_SandyPluginInstance,
|
||||
getFlipperLib,
|
||||
_SandyPluginDefinition,
|
||||
} from 'flipper-plugin';
|
||||
} from 'flipper-plugin-core';
|
||||
import {createServerAddOnControls} from './utils/createServerAddOnControls';
|
||||
import isProduction from './utils/isProduction';
|
||||
|
||||
@@ -135,17 +135,23 @@ export default abstract class AbstractClient extends EventEmitter {
|
||||
initialState?: Record<string, any>,
|
||||
) {
|
||||
try {
|
||||
this.sandyPluginStates.set(
|
||||
plugin.id,
|
||||
new _SandyPluginInstance(
|
||||
const pluginInstance = new _SandyPluginInstance(
|
||||
this.serverAddOnControls,
|
||||
getFlipperLib(),
|
||||
plugin,
|
||||
this,
|
||||
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) {
|
||||
console.error(`Failed to start plugin '${plugin.id}': `, e);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {FlipperLib, Notification} from 'flipper-plugin';
|
||||
import {FlipperLib, Notification} from 'flipper-plugin-core';
|
||||
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
|
||||
|
||||
type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action';
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getLatestCompatibleVersionOfEachPlugin,
|
||||
} from '../plugins';
|
||||
import {BundledPluginDetails, InstalledPluginDetails} from 'flipper-common';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
let loadDynamicPluginsMock: jest.Mock;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
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 {
|
||||
isArchived = true;
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
createState,
|
||||
getFlipperLib,
|
||||
CrashLogListener,
|
||||
} from 'flipper-plugin';
|
||||
} from 'flipper-plugin-core';
|
||||
import {
|
||||
DeviceLogEntry,
|
||||
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)
|
||||
private readonly serverAddOnControls: ServerAddOnControls;
|
||||
|
||||
constructor(flipperServer: FlipperServer, description: DeviceDescription) {
|
||||
constructor(
|
||||
flipperServer: FlipperServer,
|
||||
description: DeviceDescription,
|
||||
private pluginErrorHandler?: (msg: string) => void,
|
||||
) {
|
||||
this.flipperServer = flipperServer;
|
||||
this.description = description;
|
||||
this.serverAddOnControls = createServerAddOnControls(this.flipperServer);
|
||||
@@ -341,9 +345,7 @@ export default class BaseDevice implements Device {
|
||||
this.hasDevicePlugins = true;
|
||||
if (plugin instanceof _SandyPluginDefinition) {
|
||||
try {
|
||||
this.sandyPluginStates.set(
|
||||
plugin.id,
|
||||
new _SandyDevicePluginInstance(
|
||||
const pluginInstance = new _SandyDevicePluginInstance(
|
||||
this.serverAddOnControls,
|
||||
getFlipperLib(),
|
||||
plugin,
|
||||
@@ -351,8 +353,11 @@ export default class BaseDevice implements Device {
|
||||
// break circular dep, one of those days again...
|
||||
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) {
|
||||
console.error(`Failed to start device plugin '${plugin.id}': `, e);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {DeviceOS, DeviceType} from 'flipper-plugin';
|
||||
import type {DeviceOS, DeviceType} from 'flipper-plugin-core';
|
||||
import {DeviceSpec} from 'flipper-common';
|
||||
import BaseDevice from './BaseDevice';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
TestUtils,
|
||||
_SandyPluginDefinition,
|
||||
_setFlipperLibImplementation,
|
||||
} from 'flipper-plugin';
|
||||
} from 'flipper-plugin-core';
|
||||
import {default as ArchivedDevice} from '../ArchivedDevice';
|
||||
import {TestDevice} from '../TestDevice';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import {assertNever, DownloadFileUpdate} from 'flipper-common';
|
||||
import {FlipperLib, DownloadFileResponse} from 'flipper-plugin';
|
||||
import {FlipperLib, DownloadFileResponse} from 'flipper-plugin-core';
|
||||
import {RenderHost} from '../RenderHost';
|
||||
|
||||
export const downloadFileFactory =
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {RemoteServerContext, FlipperLib} from 'flipper-plugin';
|
||||
import {RemoteServerContext, FlipperLib} from 'flipper-plugin-core';
|
||||
import {
|
||||
BufferEncoding,
|
||||
ExecOptions,
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
ConcretePluginDetails,
|
||||
} 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 isPluginVersionMoreRecent from './utils/isPluginVersionMoreRecent';
|
||||
import {getRenderHostInstance} from './RenderHost';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"path": "../flipper-common"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-plugin"
|
||||
"path": "../flipper-plugin-core"
|
||||
},
|
||||
{
|
||||
"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
|
||||
*/
|
||||
|
||||
import type {ReactElement} from 'react';
|
||||
import {Logger} from '../utils/Logger';
|
||||
import {Device} from './DevicePlugin';
|
||||
import {NormalizedMenuEntry} from './MenuEntry';
|
||||
import {RealFlipperClient} from './Plugin';
|
||||
import {Notification} from './Notification';
|
||||
import {DetailSidebarProps} from '../ui/DetailSidebar';
|
||||
import {
|
||||
ExecOptions,
|
||||
ExecOut,
|
||||
@@ -109,9 +109,11 @@ export interface FlipperLib {
|
||||
writeTextToClipboard(text: string): void;
|
||||
openLink(url: string): void;
|
||||
showNotification(pluginKey: string, notification: Notification): void;
|
||||
DetailsSidebarImplementation?(
|
||||
props: DetailSidebarProps,
|
||||
): React.ReactElement | null;
|
||||
DetailsSidebarImplementation?(props: {
|
||||
children: any;
|
||||
width?: number;
|
||||
minWidth?: number;
|
||||
}): ReactElement | null;
|
||||
/**
|
||||
* @returns
|
||||
* Imported file data.
|
||||
@@ -7,10 +7,11 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {ReactNode} from 'react';
|
||||
export type Notification = {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string | React.ReactNode;
|
||||
message: string | ReactNode;
|
||||
severity: 'warning' | 'error';
|
||||
timestamp?: number;
|
||||
category?: string;
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
EventsContract,
|
||||
MethodsContract,
|
||||
} from 'flipper-common';
|
||||
import type {FC} from 'react';
|
||||
|
||||
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
||||
[Key in keyof Contract]?: never;
|
||||
@@ -140,7 +141,7 @@ export type PluginFactory<
|
||||
client: PluginClient<Events, Methods, ServerAddOnEvents, ServerAddOnMethods>,
|
||||
) => object;
|
||||
|
||||
export type FlipperPluginComponent = React.FC<{}>;
|
||||
export type FlipperPluginComponent = FC<{}>;
|
||||
|
||||
export class SandyPluginInstance extends BasePluginInstance {
|
||||
static is(thing: any): thing is SandyPluginInstance {
|
||||
@@ -7,7 +7,6 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {message} from 'antd';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||
import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry';
|
||||
@@ -312,7 +311,7 @@ export abstract class BasePluginInstance {
|
||||
// msg is already specific
|
||||
// eslint-disable-next-line
|
||||
console.error(msg, e);
|
||||
message.error(msg);
|
||||
this.events.emit('error', msg);
|
||||
}
|
||||
}
|
||||
this.initialStates = undefined;
|
||||
@@ -325,7 +324,7 @@ export abstract class BasePluginInstance {
|
||||
// msg is already specific
|
||||
// eslint-disable-next-line
|
||||
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,
|
||||
DataSourceOptions as BaseDataSourceOptions,
|
||||
DataSourceOptionKey as BaseDataSourceOptionKey,
|
||||
} from '../data-source/index';
|
||||
} from '../data-source/DataSource';
|
||||
import {registerStorageAtom} from '../plugin/PluginBase';
|
||||
|
||||
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",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"flipper-common": "0.0.0",
|
||||
"flipper-plugin-core": "0.0.0",
|
||||
"immer": "^9.0.12",
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {DevicePluginClient, Device} from '../plugin/DevicePlugin';
|
||||
import {DevicePluginClient, Device} from 'flipper-plugin-core';
|
||||
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) {
|
||||
return true;
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {PluginClient} from '../plugin/Plugin';
|
||||
import {PluginClient} from 'flipper-plugin-core';
|
||||
import {usePlugin} from '../plugin/PluginContext';
|
||||
import {createState, useValue} from '../state/atom';
|
||||
import {useValue} from '../state/atom';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
|
||||
type Events = {
|
||||
inc: {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import * as TestUtils from '../test-utils/test-utils';
|
||||
import * as testPlugin from './DeviceTestPlugin';
|
||||
import {createState} from '../state/atom';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
|
||||
const testLogMessage = {
|
||||
date: new Date(),
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
|
||||
import * as TestUtils from '../test-utils/test-utils';
|
||||
import * as testPlugin from './TestPlugin';
|
||||
import {createState} from '../state/atom';
|
||||
import {PluginClient} from '../plugin/Plugin';
|
||||
import {DevicePluginClient} from '../plugin/DevicePlugin';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
import {PluginClient} from 'flipper-plugin-core';
|
||||
import {DevicePluginClient} from 'flipper-plugin-core';
|
||||
import mockConsole from 'jest-mock-console';
|
||||
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', () => {
|
||||
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",
|
||||
[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 {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* @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 {RedrawContext} from './DataSourceRendererVirtual';
|
||||
@@ -16,7 +17,7 @@ type DataSourceProps<T extends object, C> = {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -35,7 +36,7 @@ type DataSourceProps<T extends object, C> = {
|
||||
onUpdateAutoScroll?(autoScroll: boolean): void;
|
||||
emptyRenderer?:
|
||||
| null
|
||||
| ((dataView: DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||
| ((dataView: _DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,8 @@ import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
} 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 observeRect from '@reach/observe-rect';
|
||||
|
||||
@@ -39,7 +40,7 @@ type DataSourceProps<T extends object, C> = {
|
||||
/**
|
||||
* 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?
|
||||
*/
|
||||
@@ -68,7 +69,7 @@ type DataSourceProps<T extends object, C> = {
|
||||
onUpdateAutoScroll?(autoScroll: boolean): void;
|
||||
emptyRenderer?:
|
||||
| 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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,9 +66,9 @@ The events will describe how the current view should be updated to reflect the d
|
||||
|
||||
### DataSourceRendererVirtual
|
||||
|
||||
`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).
|
||||
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`.
|
||||
`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).
|
||||
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.
|
||||
|
||||
@@ -163,7 +163,7 @@ Project setup:
|
||||
|
||||
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.
|
||||
* [ ] **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.
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
export {
|
||||
DataSource,
|
||||
DataSourceView,
|
||||
_DataSourceView,
|
||||
createDataSource,
|
||||
DataSourceOptions,
|
||||
DataSourceOptionKey,
|
||||
} from './DataSource';
|
||||
_DataSourceOptions,
|
||||
_DataSourceOptionKey, // eslint-disable-next-line node/no-extraneous-import
|
||||
} from 'flipper-plugin-core';
|
||||
export {
|
||||
DataSourceRendererVirtual,
|
||||
DataSourceVirtualizer,
|
||||
|
||||
@@ -7,52 +7,22 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
export {produce, Draft} from 'immer';
|
||||
export * from 'flipper-plugin-core';
|
||||
|
||||
import styledImport from '@emotion/styled';
|
||||
export const styled = styledImport;
|
||||
|
||||
import './plugin/PluginBase';
|
||||
import './state/batch';
|
||||
|
||||
export {useValue} from './state/atom';
|
||||
|
||||
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 {
|
||||
SandyPluginContext as _SandyPluginContext,
|
||||
usePlugin,
|
||||
} 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 {Layout} from './ui/Layout';
|
||||
@@ -83,12 +53,6 @@ export {DataFormatter} from './ui/DataFormatter';
|
||||
|
||||
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 {DataTableManager} from './ui/data-table/DataTableManager';
|
||||
export {DataList} from './ui/DataList';
|
||||
@@ -127,36 +91,13 @@ export {
|
||||
ElementID,
|
||||
} from './ui/elements-inspector/ElementsInspector';
|
||||
export {useMemoize} from './utils/useMemoize';
|
||||
export {
|
||||
makeShallowSerializable as _makeShallowSerializable,
|
||||
deserializeShallowObject as _deserializeShallowObject,
|
||||
} from './utils/shallowSerialization';
|
||||
|
||||
export {createTablePlugin} from './utils/createTablePlugin';
|
||||
|
||||
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,
|
||||
// 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)
|
||||
// T69106962
|
||||
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 {SandyPluginInstance, PluginFactory} from './Plugin';
|
||||
import {SandyDevicePluginInstance, DevicePluginFactory} from './DevicePlugin';
|
||||
import {
|
||||
_SandyDevicePluginInstance,
|
||||
_DevicePluginFactory,
|
||||
_SandyPluginInstance,
|
||||
_PluginFactory,
|
||||
} from 'flipper-plugin-core';
|
||||
|
||||
export const SandyPluginContext = createContext<
|
||||
SandyPluginInstance | SandyDevicePluginInstance | undefined
|
||||
_SandyPluginInstance | _SandyDevicePluginInstance | undefined
|
||||
>(undefined);
|
||||
|
||||
export function usePluginInstance():
|
||||
| SandyPluginInstance
|
||||
| SandyDevicePluginInstance {
|
||||
| _SandyPluginInstance
|
||||
| _SandyDevicePluginInstance {
|
||||
const pluginInstance = useContext(SandyPluginContext);
|
||||
if (!pluginInstance) {
|
||||
throw new Error('Sandy Plugin context not available');
|
||||
@@ -26,14 +30,14 @@ export function usePluginInstance():
|
||||
}
|
||||
|
||||
export function usePluginInstanceMaybe():
|
||||
| SandyPluginInstance
|
||||
| SandyDevicePluginInstance
|
||||
| _SandyPluginInstance
|
||||
| _SandyDevicePluginInstance
|
||||
| undefined {
|
||||
return useContext(SandyPluginContext);
|
||||
}
|
||||
|
||||
export function usePlugin<
|
||||
Factory extends PluginFactory<any, any, any, any> | DevicePluginFactory,
|
||||
Factory extends _PluginFactory<any, any, any, any> | _DevicePluginFactory,
|
||||
>(plugin: Factory): ReturnType<Factory> {
|
||||
const pluginInstance = usePluginInstance();
|
||||
// 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 {SandyPluginContext} from './PluginContext';
|
||||
import {SandyPluginInstance} from './Plugin';
|
||||
import {SandyDevicePluginInstance} from './DevicePlugin';
|
||||
import {BasePluginInstance} from './PluginBase';
|
||||
import {
|
||||
_SandyPluginInstance,
|
||||
_SandyDevicePluginInstance,
|
||||
_BasePluginInstance,
|
||||
} from 'flipper-plugin-core';
|
||||
import {TrackingScope} from '../ui/Tracked';
|
||||
|
||||
type Props = {
|
||||
plugin: SandyPluginInstance | SandyDevicePluginInstance;
|
||||
plugin: _SandyPluginInstance | _SandyDevicePluginInstance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render a Sandy plugin container
|
||||
*/
|
||||
export const SandyPluginRenderer = memo(({plugin}: Props) => {
|
||||
if (!plugin || !(plugin instanceof BasePluginInstance)) {
|
||||
if (!plugin || !(plugin instanceof _BasePluginInstance)) {
|
||||
throw new Error('Expected plugin, got ' + plugin);
|
||||
}
|
||||
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
|
||||
*/
|
||||
|
||||
import {createState} from '../atom';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
import * as TestUtils from '../../test-utils/test-utils';
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -7,143 +7,16 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {produce, Draft, enableMapSet} from 'immer';
|
||||
import {_AtomValue, _ReadOnlyAtom} from 'flipper-plugin-core';
|
||||
import {useState, useEffect} from 'react';
|
||||
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;
|
||||
}
|
||||
|
||||
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>(atom: _ReadOnlyAtom<T>): T;
|
||||
export function useValue<T>(
|
||||
atom: ReadOnlyAtom<T> | undefined,
|
||||
atom: _ReadOnlyAtom<T> | undefined,
|
||||
defaultValue: T,
|
||||
): T;
|
||||
export function useValue<T>(
|
||||
atom: ReadOnlyAtom<T> | undefined,
|
||||
atom: _ReadOnlyAtom<T> | undefined,
|
||||
defaultValue?: T,
|
||||
): T {
|
||||
const [localValue, setLocalValue] = useState<T>(
|
||||
@@ -156,14 +29,10 @@ export function useValue<T>(
|
||||
// atom might have changed between mounting and effect setup
|
||||
// in that case, this will cause a re-render, otherwise not
|
||||
setLocalValue(atom.get());
|
||||
(atom as AtomValue<T>).subscribe(setLocalValue);
|
||||
(atom as _AtomValue<T>).subscribe(setLocalValue);
|
||||
return () => {
|
||||
(atom as AtomValue<T>).unsubscribe(setLocalValue);
|
||||
(atom as _AtomValue<T>).unsubscribe(setLocalValue);
|
||||
};
|
||||
}, [atom]);
|
||||
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 {_setBatchedUpdateImplementation} from 'flipper-plugin-core';
|
||||
|
||||
export const batch = 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;
|
||||
};
|
||||
}
|
||||
_setBatchedUpdateImplementation(unstable_batchedUpdates);
|
||||
|
||||
@@ -10,71 +10,45 @@
|
||||
import * as React from 'react';
|
||||
import type {RenderResult} from '@testing-library/react';
|
||||
import {queries} from '@testing-library/dom';
|
||||
import {
|
||||
BundledPluginDetails,
|
||||
fsConstants,
|
||||
InstalledPluginDetails,
|
||||
ServerAddOnControls,
|
||||
} from 'flipper-common';
|
||||
import {ServerAddOnControls} from 'flipper-common';
|
||||
|
||||
import {
|
||||
RealFlipperClient,
|
||||
SandyPluginInstance,
|
||||
_RealFlipperClient,
|
||||
_SandyPluginInstance,
|
||||
PluginClient,
|
||||
PluginFactory,
|
||||
} from '../plugin/Plugin';
|
||||
import {
|
||||
SandyPluginDefinition,
|
||||
FlipperPluginModule,
|
||||
FlipperDevicePluginModule,
|
||||
} from '../plugin/SandyPluginDefinition';
|
||||
import {SandyPluginRenderer} from '../plugin/PluginRenderer';
|
||||
import {
|
||||
SandyDevicePluginInstance,
|
||||
_PluginFactory,
|
||||
_SandyPluginDefinition,
|
||||
_FlipperPluginModule,
|
||||
_FlipperDevicePluginModule,
|
||||
_SandyDevicePluginInstance,
|
||||
Device,
|
||||
DeviceLogListener,
|
||||
CrashLogListener,
|
||||
} from '../plugin/DevicePlugin';
|
||||
import {BasePluginInstance} from '../plugin/PluginBase';
|
||||
import {FlipperLib} from '../plugin/FlipperLib';
|
||||
import {stubLogger} from '../utils/Logger';
|
||||
import {Idler} from '../utils/Idler';
|
||||
import {createState} from '../state/atom';
|
||||
import {
|
||||
DeviceLogEntry,
|
||||
FlipperServer,
|
||||
FlipperServerCommands,
|
||||
} from 'flipper-common';
|
||||
_BasePluginInstance,
|
||||
FlipperLib,
|
||||
_stubLogger,
|
||||
Idler,
|
||||
createState,
|
||||
TestUtils,
|
||||
_StartPluginOptions,
|
||||
} from 'flipper-plugin-core';
|
||||
import {SandyPluginRenderer} from '../plugin/PluginRenderer';
|
||||
import {DeviceLogEntry} from 'flipper-common';
|
||||
|
||||
declare const process: any;
|
||||
declare const electronRequire: any;
|
||||
|
||||
type Renderer = RenderResult<typeof queries>;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
type ExtractClientType<Module extends FlipperPluginModule<any>> = Parameters<
|
||||
type ExtractClientType<Module extends _FlipperPluginModule<any>> = Parameters<
|
||||
Module['plugin']
|
||||
>[0];
|
||||
|
||||
type ExtractMethodsType<Module extends FlipperPluginModule<any>> =
|
||||
type ExtractMethodsType<Module extends _FlipperPluginModule<any>> =
|
||||
ExtractClientType<Module> extends PluginClient<any, infer Methods>
|
||||
? Methods
|
||||
: never;
|
||||
|
||||
type ExtractEventsType<Module extends FlipperPluginModule<any>> =
|
||||
type ExtractEventsType<Module extends _FlipperPluginModule<any>> =
|
||||
ExtractClientType<Module> extends PluginClient<infer Events, any>
|
||||
? Events
|
||||
: never;
|
||||
@@ -125,7 +99,7 @@ interface BasePluginResult {
|
||||
serverAddOnControls: ServerAddOnControls;
|
||||
}
|
||||
|
||||
interface StartPluginResult<Module extends FlipperPluginModule<any>>
|
||||
interface StartPluginResult<Module extends _FlipperPluginModule<any>>
|
||||
extends BasePluginResult {
|
||||
/**
|
||||
* the instantiated plugin for this test
|
||||
@@ -173,7 +147,7 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>>
|
||||
): void;
|
||||
}
|
||||
|
||||
interface StartDevicePluginResult<Module extends FlipperDevicePluginModule>
|
||||
interface StartDevicePluginResult<Module extends _FlipperDevicePluginModule>
|
||||
extends BasePluginResult {
|
||||
/**
|
||||
* the instantiated plugin for this test
|
||||
@@ -189,24 +163,14 @@ interface StartDevicePluginResult<Module extends FlipperDevicePluginModule>
|
||||
sendLogEntry(logEntry: DeviceLogEntry): void;
|
||||
}
|
||||
|
||||
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 startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
export function startPlugin<Module extends _FlipperPluginModule<any>>(
|
||||
module: Module,
|
||||
options?: StartPluginOptions,
|
||||
options?: _StartPluginOptions,
|
||||
): StartPluginResult<Module> {
|
||||
const {act} = electronRequire('@testing-library/react');
|
||||
|
||||
const definition = new SandyPluginDefinition(
|
||||
createMockPluginDetails(),
|
||||
const definition = new _SandyPluginDefinition(
|
||||
TestUtils.createMockPluginDetails(),
|
||||
module,
|
||||
);
|
||||
if (definition.isDevicePlugin) {
|
||||
@@ -215,12 +179,12 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
);
|
||||
}
|
||||
|
||||
const sendStub = createStubFunction();
|
||||
const flipperUtils = createMockFlipperLib(options);
|
||||
const sendStub = TestUtils.createStubFunction();
|
||||
const flipperUtils = TestUtils.createMockFlipperLib(options);
|
||||
const testDevice = createMockDevice(options);
|
||||
const appName = 'TestApplication';
|
||||
const deviceName = 'TestDevice';
|
||||
const fakeFlipperClient: RealFlipperClient = {
|
||||
const fakeFlipperClient: _RealFlipperClient = {
|
||||
id: `${appName}#${testDevice.os}#${deviceName}#${testDevice.serial}`,
|
||||
plugins: new Set([definition.id]),
|
||||
query: {
|
||||
@@ -263,7 +227,7 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
|
||||
const serverAddOnControls = createServerAddOnControlsMock();
|
||||
|
||||
const pluginInstance = new SandyPluginInstance(
|
||||
const pluginInstance = new _SandyPluginInstance(
|
||||
serverAddOnControls,
|
||||
flipperUtils,
|
||||
definition,
|
||||
@@ -305,9 +269,9 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
return res;
|
||||
}
|
||||
|
||||
export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
||||
export function renderPlugin<Module extends _FlipperPluginModule<any>>(
|
||||
module: Module,
|
||||
options?: StartPluginOptions,
|
||||
options?: _StartPluginOptions,
|
||||
): StartPluginResult<Module> & {
|
||||
renderer: Renderer;
|
||||
act: (cb: () => void) => void;
|
||||
@@ -315,7 +279,7 @@ export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
||||
// prevent bundling in UI bundle
|
||||
const {render, act} = electronRequire('@testing-library/react');
|
||||
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} />);
|
||||
|
||||
@@ -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,
|
||||
options?: StartPluginOptions,
|
||||
options?: _StartPluginOptions,
|
||||
): StartDevicePluginResult<Module> {
|
||||
const {act} = electronRequire('@testing-library/react');
|
||||
|
||||
const definition = new SandyPluginDefinition(
|
||||
createMockPluginDetails({pluginType: 'device'}),
|
||||
const definition = new _SandyPluginDefinition(
|
||||
TestUtils.createMockPluginDetails({pluginType: 'device'}),
|
||||
module,
|
||||
);
|
||||
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 serverAddOnControls = createServerAddOnControlsMock();
|
||||
const pluginInstance = new SandyDevicePluginInstance(
|
||||
const pluginInstance = new _SandyDevicePluginInstance(
|
||||
serverAddOnControls,
|
||||
flipperLib,
|
||||
definition,
|
||||
@@ -376,9 +340,9 @@ export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
||||
return res;
|
||||
}
|
||||
|
||||
export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
||||
export function renderDevicePlugin<Module extends _FlipperDevicePluginModule>(
|
||||
module: Module,
|
||||
options?: StartPluginOptions,
|
||||
options?: _StartPluginOptions,
|
||||
): StartDevicePluginResult<Module> & {
|
||||
renderer: Renderer;
|
||||
act: (cb: () => void) => void;
|
||||
@@ -387,7 +351,7 @@ export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
||||
|
||||
const res = startDevicePlugin(module, options);
|
||||
// @ts-ignore hidden api
|
||||
const pluginInstance: SandyDevicePluginInstance = (res as any)
|
||||
const pluginInstance: _SandyDevicePluginInstance = (res as any)
|
||||
._backingInstance;
|
||||
|
||||
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(
|
||||
pluginInstance: BasePluginInstance,
|
||||
pluginInstance: _BasePluginInstance,
|
||||
serverAddOnControls: ServerAddOnControls,
|
||||
): BasePluginResult {
|
||||
return {
|
||||
@@ -491,85 +399,7 @@ function createBasePluginResult(
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
function createMockDevice(options?: StartPluginOptions): Device & {
|
||||
function createMockDevice(options?: _StartPluginOptions): Device & {
|
||||
addLogEntry(entry: DeviceLogEntry): void;
|
||||
} {
|
||||
const logListeners: (undefined | DeviceLogListener)[] = [];
|
||||
@@ -608,18 +438,18 @@ function createMockDevice(options?: StartPluginOptions): Device & {
|
||||
addLogEntry(entry: DeviceLogEntry) {
|
||||
logListeners.forEach((f) => f?.(entry));
|
||||
},
|
||||
executeShell: createStubFunction(),
|
||||
clearLogs: createStubFunction(),
|
||||
forwardPort: createStubFunction(),
|
||||
executeShell: TestUtils.createStubFunction(),
|
||||
clearLogs: TestUtils.createStubFunction(),
|
||||
forwardPort: TestUtils.createStubFunction(),
|
||||
get isConnected() {
|
||||
return this.connected.get();
|
||||
},
|
||||
installApp(_: string) {
|
||||
return Promise.resolve();
|
||||
},
|
||||
navigateToLocation: createStubFunction(),
|
||||
screenshot: createStubFunction(),
|
||||
sendMetroCommand: createStubFunction(),
|
||||
navigateToLocation: TestUtils.createStubFunction(),
|
||||
screenshot: TestUtils.createStubFunction(),
|
||||
sendMetroCommand: TestUtils.createStubFunction(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -638,38 +468,22 @@ function createStubIdler(): Idler {
|
||||
};
|
||||
}
|
||||
|
||||
export function createFlipperServerMock(
|
||||
overrides?: Partial<FlipperServerCommands>,
|
||||
): FlipperServer {
|
||||
function createServerAddOnControlsMock(): ServerAddOnControls {
|
||||
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(),
|
||||
start: TestUtils.createStubFunction(),
|
||||
stop: TestUtils.createStubFunction(),
|
||||
sendMessage: TestUtils.createStubFunction(),
|
||||
receiveMessage: TestUtils.createStubFunction(),
|
||||
receiveAnyMessage: TestUtils.createStubFunction(),
|
||||
unsubscribePlugin: TestUtils.createStubFunction(),
|
||||
unsubscribe: TestUtils.createStubFunction(),
|
||||
};
|
||||
}
|
||||
|
||||
function createServerAddOnControlsMock(): ServerAddOnControls {
|
||||
return {
|
||||
start: createStubFunction(),
|
||||
stop: createStubFunction(),
|
||||
sendMessage: createStubFunction(),
|
||||
receiveMessage: createStubFunction(),
|
||||
receiveAnyMessage: createStubFunction(),
|
||||
unsubscribePlugin: createStubFunction(),
|
||||
unsubscribe: createStubFunction(),
|
||||
};
|
||||
}
|
||||
export const createMockFlipperLib = TestUtils.createMockFlipperLib;
|
||||
export const createMockPluginDetails = TestUtils.createMockPluginDetails;
|
||||
export const createTestPlugin = TestUtils.createTestPlugin;
|
||||
export const createTestDevicePlugin = TestUtils.createTestDevicePlugin;
|
||||
export const createMockBundledPluginDetails =
|
||||
TestUtils.createMockBundledPluginDetails;
|
||||
export const createFlipperServerMock = TestUtils.createFlipperServerMock;
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
import {Button, Typography} from 'antd';
|
||||
import {isPlainObject, pad} from 'lodash';
|
||||
import React, {createElement, Fragment, isValidElement, useState} from 'react';
|
||||
import {tryGetFlipperLibImplementation} from '../plugin/FlipperLib';
|
||||
import {safeStringify} from '../utils/safeStringify';
|
||||
import {_tryGetFlipperLibImplementation} from 'flipper-plugin-core';
|
||||
import {safeStringify} from 'flipper-plugin-core';
|
||||
import {urlRegex} from '../utils/urlRegex';
|
||||
import {useTableRedraw} from '../data-source/index';
|
||||
import {theme} from './theme';
|
||||
@@ -196,7 +196,7 @@ export function TruncateHelper({
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => {
|
||||
tryGetFlipperLibImplementation()?.writeTextToClipboard(value);
|
||||
_tryGetFlipperLibImplementation()?.writeTextToClipboard(value);
|
||||
}}
|
||||
size="small"
|
||||
type="text"
|
||||
|
||||
@@ -22,7 +22,7 @@ import {theme} from './theme';
|
||||
import styled from '@emotion/styled';
|
||||
import {DataTableManager} from './data-table/DataTableManager';
|
||||
import {useAssertStableRef} from '../utils/useAssertStableRef';
|
||||
import {DataSource} from '../data-source';
|
||||
import {DataSource} from 'flipper-plugin-core';
|
||||
import {useMakeStableCallback} from '../utils/useMakeStableCallback';
|
||||
|
||||
const {Text} = Typography;
|
||||
@@ -95,7 +95,9 @@ export const DataList: (<T extends object>(
|
||||
}: DataListProps<T>) {
|
||||
// if a tableManagerRef is provided, we piggy back on that same ref
|
||||
// eslint-disable-next-line
|
||||
const tableManagerRef = tableProps.tableManagerRef ?? createRef<undefined | DataTableManager<T>>();
|
||||
const tableManagerRef =
|
||||
tableProps.tableManagerRef ??
|
||||
createRef<undefined | DataTableManager<T>>();
|
||||
|
||||
useAssertStableRef(onRenderItem, 'onRenderItem');
|
||||
useAssertStableRef(enableArrow, 'enableArrow');
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {tryGetFlipperLibImplementation} from '../plugin/FlipperLib';
|
||||
import {_tryGetFlipperLibImplementation} from 'flipper-plugin-core';
|
||||
import {Layout} from './Layout';
|
||||
|
||||
export type DetailSidebarProps = {
|
||||
@@ -19,7 +19,7 @@ export type DetailSidebarProps = {
|
||||
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
export function DetailSidebar(props: DetailSidebarProps) {
|
||||
const lib = tryGetFlipperLibImplementation();
|
||||
const lib = _tryGetFlipperLibImplementation();
|
||||
if (lib?.DetailsSidebarImplementation) {
|
||||
return <lib.DetailsSidebarImplementation {...props} />;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
*/
|
||||
|
||||
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 {renderReactRoot} from '../utils/renderReactRoot';
|
||||
import {Layout} from './Layout';
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
FileEncoding,
|
||||
FlipperLib,
|
||||
getFlipperLib,
|
||||
} from '../plugin/FlipperLib';
|
||||
} from 'flipper-plugin-core';
|
||||
import {fromUint8Array} from 'js-base64';
|
||||
import {assertNever} from 'flipper-common';
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import {Button} from 'antd';
|
||||
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 {useValue} from '../state/atom';
|
||||
|
||||
type MasterDetailProps<T> = {
|
||||
/**
|
||||
|
||||
@@ -19,12 +19,13 @@ import styled from '@emotion/styled';
|
||||
import {keyframes} from '@emotion/css';
|
||||
import reactElementToJSXString from 'react-element-to-jsx-string';
|
||||
import {SandyPluginContext} from '../plugin/PluginContext';
|
||||
import {createState, useValue} from '../state/atom';
|
||||
import {SandyDevicePluginInstance} from '../plugin/DevicePlugin';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
import {useValue} from '../state/atom';
|
||||
import {_SandyDevicePluginInstance} from 'flipper-plugin-core';
|
||||
import {Layout} from './Layout';
|
||||
import {BulbTwoTone} from '@ant-design/icons';
|
||||
import type {TooltipPlacement} from 'antd/lib/tooltip';
|
||||
import {SandyPluginInstance} from '../plugin/Plugin';
|
||||
import {_SandyPluginInstance} from 'flipper-plugin-core';
|
||||
import {theme} from './theme';
|
||||
import {Tracked} from './Tracked';
|
||||
import {sha256} from '../utils/sha256';
|
||||
@@ -37,7 +38,7 @@ const storageKey = `FLIPPER_NUX_STATE`;
|
||||
|
||||
export async function getNuxKey(
|
||||
elem: React.ReactNode,
|
||||
currentPlugin?: SandyPluginInstance | SandyDevicePluginInstance,
|
||||
currentPlugin?: _SandyPluginInstance | _SandyDevicePluginInstance,
|
||||
): Promise<string> {
|
||||
const hash = await sha256(reactElementToJSXString(elem));
|
||||
return `${currentPlugin?.definition.id ?? 'flipper'}:${hash}`;
|
||||
@@ -59,14 +60,14 @@ export function createNuxManager() {
|
||||
return {
|
||||
async markRead(
|
||||
elem: React.ReactNode,
|
||||
currentPlugin?: SandyPluginInstance | SandyDevicePluginInstance,
|
||||
currentPlugin?: _SandyPluginInstance | _SandyDevicePluginInstance,
|
||||
): Promise<void> {
|
||||
readMap[await getNuxKey(elem, currentPlugin)] = true;
|
||||
save();
|
||||
},
|
||||
async isRead(
|
||||
elem: React.ReactNode,
|
||||
currentPlugin?: SandyPluginInstance | SandyDevicePluginInstance,
|
||||
currentPlugin?: _SandyPluginInstance | _SandyDevicePluginInstance,
|
||||
): Promise<boolean> {
|
||||
return !!readMap[await getNuxKey(elem, currentPlugin)];
|
||||
},
|
||||
|
||||
@@ -26,8 +26,8 @@ import {getSortedKeys} from './utils';
|
||||
import React from 'react';
|
||||
import {useHighlighter, HighlightManager} from '../Highlight';
|
||||
import {Dropdown, Menu, Tooltip} from 'antd';
|
||||
import {tryGetFlipperLibImplementation} from '../../plugin/FlipperLib';
|
||||
import {safeStringify} from '../../utils/safeStringify';
|
||||
import {_tryGetFlipperLibImplementation} from 'flipper-plugin-core';
|
||||
import {safeStringify} from 'flipper-plugin-core';
|
||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||
import {theme} from '../theme';
|
||||
|
||||
@@ -615,7 +615,7 @@ export const DataInspectorNode: React.FC<DataInspectorProps> = memo(
|
||||
}
|
||||
|
||||
function getContextMenu() {
|
||||
const lib = tryGetFlipperLibImplementation();
|
||||
const lib = _tryGetFlipperLibImplementation();
|
||||
const extraItems = additionalContextMenuItems
|
||||
? [
|
||||
additionalContextMenuItems(parentPath, value, name),
|
||||
|
||||
@@ -26,8 +26,6 @@ import {Percentage} from '../../utils/widthUtils';
|
||||
import {
|
||||
DataSourceRendererVirtual,
|
||||
DataSourceRendererStatic,
|
||||
DataSource,
|
||||
DataSourceView,
|
||||
DataSourceVirtualizer,
|
||||
} from '../../data-source/index';
|
||||
import {
|
||||
@@ -53,7 +51,11 @@ import {Formatter} from '../DataFormatter';
|
||||
import {usePluginInstanceMaybe} from '../../plugin/PluginContext';
|
||||
import {debounce} from 'lodash';
|
||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||
import {createDataSource} from '../../state/createDataSource';
|
||||
import {
|
||||
createDataSource,
|
||||
DataSource,
|
||||
_DataSourceView,
|
||||
} from 'flipper-plugin-core';
|
||||
import {HighlightProvider} from '../Highlight';
|
||||
import {useLatestRef} from '../../utils/useLatestRef';
|
||||
|
||||
@@ -77,7 +79,7 @@ type DataTableBaseProps<T = any> = {
|
||||
onContextMenu?: (selection: undefined | T) => React.ReactElement;
|
||||
onRenderEmpty?:
|
||||
| null
|
||||
| ((dataView?: DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||
| ((dataView?: _DataSourceView<T, T[keyof T]>) => React.ReactElement);
|
||||
};
|
||||
|
||||
export type ItemRenderer<T> = (
|
||||
@@ -771,7 +773,7 @@ function syncRecordsToDataSource<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} />
|
||||
);
|
||||
}
|
||||
@@ -780,7 +782,7 @@ function EmptyTable<T>({
|
||||
dataView,
|
||||
dataManager,
|
||||
}: {
|
||||
dataView?: DataSourceView<T, T[keyof T]>;
|
||||
dataView?: _DataSourceView<T, T[keyof T]>;
|
||||
dataManager?: DataTableManager<T>;
|
||||
}) {
|
||||
const resetFilters = useCallback(() => {
|
||||
|
||||
@@ -10,13 +10,10 @@
|
||||
import type {DataTableColumn} from './DataTable';
|
||||
import {Percentage} from '../../utils/widthUtils';
|
||||
import {MutableRefObject, Reducer} from 'react';
|
||||
import {
|
||||
DataSource,
|
||||
DataSourceView,
|
||||
DataSourceVirtualizer,
|
||||
} from '../../data-source/index';
|
||||
import {DataSourceVirtualizer} from '../../data-source/index';
|
||||
import produce, {castDraft, immerable, original} from 'immer';
|
||||
import {theme} from '../theme';
|
||||
import {DataSource, _DataSourceView} from 'flipper-plugin-core';
|
||||
|
||||
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
||||
export type Sorting<T = any> = {
|
||||
@@ -121,7 +118,7 @@ type DataManagerActions<T> =
|
||||
|
||||
type DataManagerConfig<T> = {
|
||||
dataSource: DataSource<T, T[keyof T]>;
|
||||
dataView: DataSourceView<T, T[keyof T]>;
|
||||
dataView: _DataSourceView<T, T[keyof T]>;
|
||||
defaultColumns: DataTableColumn<T>[];
|
||||
scope: string;
|
||||
onSelect: undefined | ((item: T | undefined, items: T[]) => void);
|
||||
@@ -299,7 +296,7 @@ export const dataTableManagerReducer = produce<
|
||||
}
|
||||
case 'setColumnFilterFromSelection': {
|
||||
const items = getSelectedItems(
|
||||
config.dataView as DataSourceView<any, any>,
|
||||
config.dataView as _DataSourceView<any, any>,
|
||||
draft.selection,
|
||||
);
|
||||
items.forEach((item, index) => {
|
||||
@@ -376,7 +373,7 @@ export type DataTableManager<T> = {
|
||||
toggleColumnVisibility(column: keyof T): void;
|
||||
sortColumn(column: keyof T, direction?: SortDirection): void;
|
||||
setSearchValue(value: string, addToHistory?: boolean): void;
|
||||
dataView: DataSourceView<T, T[keyof T]>;
|
||||
dataView: _DataSourceView<T, T[keyof T]>;
|
||||
toggleSearchValue(): void;
|
||||
toggleHighlightSearch(): void;
|
||||
setSearchHighlightColor(color: string): void;
|
||||
@@ -386,7 +383,7 @@ export type DataTableManager<T> = {
|
||||
};
|
||||
|
||||
export function createDataTableManager<T>(
|
||||
dataView: DataSourceView<T, T[keyof T]>,
|
||||
dataView: _DataSourceView<T, T[keyof T]>,
|
||||
dispatch: DataTableDispatch<T>,
|
||||
stateRef: MutableRefObject<DataManagerState<T>>,
|
||||
): DataTableManager<T> {
|
||||
@@ -535,14 +532,14 @@ function addColumnFilter<T>(
|
||||
}
|
||||
|
||||
export function getSelectedItem<T>(
|
||||
dataView: DataSourceView<T, T[keyof T]>,
|
||||
dataView: _DataSourceView<T, T[keyof T]>,
|
||||
selection: Selection,
|
||||
): T | undefined {
|
||||
return selection.current < 0 ? undefined : dataView.get(selection.current);
|
||||
}
|
||||
|
||||
export function getSelectedItems<T>(
|
||||
dataView: DataSourceView<T, T[keyof T]>,
|
||||
dataView: _DataSourceView<T, T[keyof T]>,
|
||||
selection: Selection,
|
||||
): T[] {
|
||||
return [...selection.items]
|
||||
|
||||
@@ -19,10 +19,12 @@ import {
|
||||
Selection,
|
||||
} from './DataTableManager';
|
||||
import React from 'react';
|
||||
import {tryGetFlipperLibImplementation} from '../../plugin/FlipperLib';
|
||||
import {
|
||||
_tryGetFlipperLibImplementation,
|
||||
_DataSourceView,
|
||||
} from 'flipper-plugin-core';
|
||||
import {DataTableColumn} from './DataTable';
|
||||
import {toFirstUpper} from '../../utils/toFirstUpper';
|
||||
import {DataSourceView} from '../../data-source/index';
|
||||
import {renderColumnValue} from './TableRow';
|
||||
import {textContent} from '../../utils/textContent';
|
||||
import {theme} from '../theme';
|
||||
@@ -31,7 +33,7 @@ const {Item, SubMenu} = Menu;
|
||||
const {Option} = Select;
|
||||
|
||||
export function tableContextMenuFactory<T>(
|
||||
dataView: DataSourceView<T, T[keyof T]>,
|
||||
dataView: _DataSourceView<T, T[keyof T]>,
|
||||
dispatch: DataTableDispatch<T>,
|
||||
selection: Selection,
|
||||
highlightSearchSetting: SearchHighlightSetting,
|
||||
@@ -45,7 +47,7 @@ export function tableContextMenuFactory<T>(
|
||||
onContextMenu?: (selection: undefined | T) => React.ReactElement,
|
||||
sideBySideOption?: React.ReactElement,
|
||||
) {
|
||||
const lib = tryGetFlipperLibImplementation();
|
||||
const lib = _tryGetFlipperLibImplementation();
|
||||
if (!lib) {
|
||||
return (
|
||||
<Menu>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import React, {createRef} from 'react';
|
||||
import {DataTable, DataTableColumn} from '../DataTable';
|
||||
import {render, act} from '@testing-library/react';
|
||||
import {createDataSource} from '../../../state/createDataSource';
|
||||
import {createDataSource} from 'flipper-plugin-core';
|
||||
import {computeDataTableFilter, DataTableManager} from '../DataTableManager';
|
||||
import {Button} from 'antd';
|
||||
import {sleep} from 'flipper-common';
|
||||
|
||||
@@ -16,8 +16,8 @@ import {theme} from '../theme';
|
||||
import {Layout} from '../Layout';
|
||||
import {
|
||||
getFlipperLib,
|
||||
tryGetFlipperLibImplementation,
|
||||
} from '../../plugin/FlipperLib';
|
||||
_tryGetFlipperLibImplementation,
|
||||
} from 'flipper-plugin-core';
|
||||
import {DownOutlined, RightOutlined} from '@ant-design/icons';
|
||||
|
||||
const {Text} = Typography;
|
||||
@@ -533,7 +533,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
|
||||
};
|
||||
|
||||
isDarwin =
|
||||
tryGetFlipperLibImplementation()?.environmentInfo.os.platform === 'darwin';
|
||||
_tryGetFlipperLibImplementation()?.environmentInfo.os.platform === 'darwin';
|
||||
|
||||
onKeyDown = (e: KeyboardEvent<any>) => {
|
||||
const {selected} = this.props;
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
*/
|
||||
|
||||
import {notification, Typography} from 'antd';
|
||||
import {DataSource} from '../data-source/index';
|
||||
import {DataSource} from 'flipper-plugin-core';
|
||||
import React from 'react';
|
||||
import {PluginClient} from '../plugin/Plugin';
|
||||
import {PluginClient} from 'flipper-plugin-core';
|
||||
import {usePlugin} from '../plugin/PluginContext';
|
||||
import {createState} from '../state/atom';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
import {DataTableColumn} from '../ui/data-table/DataTable';
|
||||
import {MasterDetail} from '../ui/MasterDetail';
|
||||
import {createDataSource} from '../state/createDataSource';
|
||||
import {createDataSource} from 'flipper-plugin-core';
|
||||
|
||||
type PluginResult<Raw, Row> = {
|
||||
plugin(client: PluginClient<Record<string, Raw | {}>>): {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {createState, useValue} from '../state/atom';
|
||||
import {createState} from 'flipper-plugin-core';
|
||||
import {useValue} from '../state/atom';
|
||||
import React, {ReactPortal} from 'react';
|
||||
import {createPortal, unmountComponentAtNode} from 'react-dom';
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
import {Logger} from 'flipper-common';
|
||||
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,
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"references": [
|
||||
{
|
||||
"path": "../flipper-common"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-plugin-core"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"flipper-common": "0.0.0",
|
||||
"flipper-frontend-core": "0.0.0",
|
||||
"flipper-plugin": "0.0.0",
|
||||
"flipper-plugin-core": "0.0.0",
|
||||
"immer": "^9.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ClientConnection,
|
||||
BaseDevice,
|
||||
} from 'flipper-frontend-core';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||
|
||||
export class HeadlessClient extends AbstractClient {
|
||||
constructor(
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
getRenderHostInstance,
|
||||
isSandyPlugin,
|
||||
} from 'flipper-frontend-core';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||
|
||||
export class HeadlessPluginInitializer extends AbstractPluginInitializer {
|
||||
protected async getFlipperVersion() {
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
FlipperCompanionAvailablePlugin,
|
||||
} from 'flipper-common';
|
||||
import {BaseDevice} from 'flipper-frontend-core';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||
import {isAtom} from 'flipper-plugin';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin-core';
|
||||
import {isAtom} from 'flipper-plugin-core';
|
||||
import {HeadlessClient} from './HeadlessClient';
|
||||
import {FlipperServerCompanionEnv} from './init';
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import {FlipperServer, getLogger} from 'flipper-common';
|
||||
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 {HeadlessPluginInitializer} from './HeadlessPluginInitializer';
|
||||
import {initializeFlipperLibImplementation} from './initializeFlipperLibImplementation';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {Logger, _setFlipperLibImplementation} from 'flipper-plugin';
|
||||
import {Logger, _setFlipperLibImplementation} from 'flipper-plugin-core';
|
||||
import {baseFlipperLibImplementation, RenderHost} from 'flipper-frontend-core';
|
||||
|
||||
export function initializeFlipperLibImplementation(
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"path": "../flipper-frontend-core"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-plugin"
|
||||
"path": "../flipper-plugin-core"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"axios": "^0.26.0",
|
||||
"flipper-common": "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-server-companion": "0.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {DeviceLogEntry} from 'flipper-plugin';
|
||||
import {DeviceLogEntry} from 'flipper-plugin-core';
|
||||
import {parseAndroidCrash, shouldParseAndroidLog} from '../AndroidCrashUtils';
|
||||
|
||||
function getAndroidLog(
|
||||
|
||||
@@ -12,9 +12,7 @@ import {
|
||||
ServerAddOnStartDetails,
|
||||
} from 'flipper-common';
|
||||
import {assertNotNull} from '../comms/Utilities';
|
||||
// Special subset of flipper-plugin exports designed for server-side usage
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import * as FlipperPluginSDK from 'flipper-plugin/src/server';
|
||||
import * as FlipperPluginSDK from 'flipper-plugin-core';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"path": "../flipper-common"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-plugin"
|
||||
"path": "../flipper-plugin-core"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-server-companion"
|
||||
|
||||
@@ -23,7 +23,7 @@ import {homedir} from 'os';
|
||||
const uiSourceDirs = [
|
||||
'flipper-ui-browser',
|
||||
'flipper-ui-core',
|
||||
'flipper-plugin',
|
||||
'flipper-plugin-core',
|
||||
'flipper-common',
|
||||
];
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
"flipper-common",
|
||||
"flipper-frontend-core",
|
||||
"flipper-plugin",
|
||||
"flipper-plugin-core",
|
||||
"flipper-server-companion",
|
||||
"flipper-server-core",
|
||||
"flipper-ui-core",
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
{
|
||||
"path": "../flipper-plugin"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-plugin-core"
|
||||
},
|
||||
{
|
||||
"path": "../flipper-server"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user