Restore TimeMachine in Flipper
Summary:
The goal is to restore the *TimeMachine* functionality in Flipper while the new UI Debugger is working in a revamped version. This version was previously removed in D42573698 (b31f8c8755).
This time machine functionality will only be available for *LithoView* that are backed by a *ComponentTree*.
Below you can find a high-level overview of the current schema:
{F884011465}
Reviewed By: LukeDefeo, adityasharat
Differential Revision: D43574130
fbshipit-source-id: fe9d661e5fcce9655e70be4785652e7048dade54
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7d99913416
commit
fa74ebba68
@@ -9,7 +9,14 @@ package com.facebook.flipper.plugins.inspector;
|
|||||||
|
|
||||||
import com.facebook.flipper.core.FlipperObject;
|
import com.facebook.flipper.core.FlipperObject;
|
||||||
import com.facebook.flipper.core.FlipperValue;
|
import com.facebook.flipper.core.FlipperValue;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class InspectorValue<T> implements FlipperValue {
|
public class InspectorValue<T> implements FlipperValue {
|
||||||
|
|
||||||
@@ -30,6 +37,7 @@ public class InspectorValue<T> implements FlipperValue {
|
|||||||
public static final Type<String> Enum = new Type<>("enum");
|
public static final Type<String> Enum = new Type<>("enum");
|
||||||
public static final Type<Integer> Color = new Type<>("color");
|
public static final Type<Integer> Color = new Type<>("color");
|
||||||
public static final Type<Picker> Picker = new Type<>("picker");
|
public static final Type<Picker> Picker = new Type<>("picker");
|
||||||
|
public static final Type<Timeline> Timeline = new Type<>("timeline");
|
||||||
|
|
||||||
private final String mName;
|
private final String mName;
|
||||||
|
|
||||||
@@ -108,4 +116,84 @@ public class InspectorValue<T> implements FlipperValue {
|
|||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A widget that represents a timeline. Each point has a moment to be placed on the timeline, and
|
||||||
|
* a key to be identified as. The current field represents the key of the point in the timeline
|
||||||
|
* that matches the current moment in time.
|
||||||
|
*/
|
||||||
|
public static final class Timeline {
|
||||||
|
public final List<TimePoint> time;
|
||||||
|
public final String current;
|
||||||
|
|
||||||
|
public Timeline(List<TimePoint> time, String current) {
|
||||||
|
Collections.sort(
|
||||||
|
time,
|
||||||
|
new Comparator<TimePoint>() {
|
||||||
|
@Override
|
||||||
|
public int compare(TimePoint stringTimePointEntry, TimePoint t1) {
|
||||||
|
return Float.compare(stringTimePointEntry.moment, t1.moment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.time = time;
|
||||||
|
this.current = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject toJson() {
|
||||||
|
final JSONArray points = new JSONArray();
|
||||||
|
for (TimePoint value : time) {
|
||||||
|
points.put(value.toJson());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new JSONObject().put("time", points).put("current", current);
|
||||||
|
} catch (JSONException t) {
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toJson().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An entry in the timeline, identified by its key. They're sorted in Flipper by moment, and are
|
||||||
|
* rendered according to the display and color. Any additional properties attached to the point
|
||||||
|
* will be displayed when it's selected.
|
||||||
|
*/
|
||||||
|
public static final class TimePoint {
|
||||||
|
public final long moment;
|
||||||
|
public final String display;
|
||||||
|
public final String color;
|
||||||
|
public final String key;
|
||||||
|
public final Map<String, String> properties;
|
||||||
|
|
||||||
|
public TimePoint(
|
||||||
|
String key, long moment, String display, String color, Map<String, String> properties) {
|
||||||
|
this.key = key;
|
||||||
|
this.moment = moment;
|
||||||
|
this.display = display;
|
||||||
|
this.color = color;
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject toJson() {
|
||||||
|
try {
|
||||||
|
return new JSONObject()
|
||||||
|
.put("moment", moment)
|
||||||
|
.put("display", display)
|
||||||
|
.put("color", color)
|
||||||
|
.put("key", key)
|
||||||
|
.put("properties", new JSONObject(properties));
|
||||||
|
} catch (JSONException t) {
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toJson().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {SketchPicker, CompactPicker} from 'react-color';
|
|||||||
import React, {KeyboardEvent} from 'react';
|
import React, {KeyboardEvent} from 'react';
|
||||||
import {HighlightContext} from '../Highlight';
|
import {HighlightContext} from '../Highlight';
|
||||||
import {parseColor} from '../../utils/parseColor';
|
import {parseColor} from '../../utils/parseColor';
|
||||||
|
import {TimelineDataDescription} from './TimelineDataDescription';
|
||||||
import {theme} from '../theme';
|
import {theme} from '../theme';
|
||||||
import {EditOutlined} from '@ant-design/icons';
|
import {EditOutlined} from '@ant-design/icons';
|
||||||
// This import is OK since it is a type-only import
|
// This import is OK since it is a type-only import
|
||||||
@@ -551,6 +552,25 @@ class DataDescriptionContainer extends PureComponent<{
|
|||||||
const highlighter = this.context;
|
const highlighter = this.context;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case 'timeline': {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TimelineDataDescription
|
||||||
|
canSetCurrent={editable}
|
||||||
|
timeline={JSON.parse(val)}
|
||||||
|
onClick={(id) => {
|
||||||
|
this.props.commit({
|
||||||
|
value: id,
|
||||||
|
keep: true,
|
||||||
|
clear: false,
|
||||||
|
set: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
case 'number':
|
case 'number':
|
||||||
return <NumberValue>{+val}</NumberValue>;
|
return <NumberValue>{+val}</NumberValue>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 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 {Component, ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import {MarkerTimeline} from '../MarkerTimeline';
|
||||||
|
import {Button} from 'antd';
|
||||||
|
import {presetColors} from './DataDescription';
|
||||||
|
import {DataInspector} from './DataInspector';
|
||||||
|
|
||||||
|
type TimePoint = {
|
||||||
|
moment: number;
|
||||||
|
display: string;
|
||||||
|
color: string;
|
||||||
|
key: string;
|
||||||
|
properties: {[key: string]: string};
|
||||||
|
};
|
||||||
|
|
||||||
|
type Timeline = {
|
||||||
|
time: TimePoint[];
|
||||||
|
current: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
canSetCurrent?: boolean;
|
||||||
|
timeline: Timeline;
|
||||||
|
onClick: (selected: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
selected: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class TimelineDataDescription extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {selected: props.timeline.current};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): ReactNode {
|
||||||
|
const moments = Object.values(this.props.timeline.time);
|
||||||
|
const firstMoment = moments[0].moment;
|
||||||
|
const points = moments.map((value) => ({
|
||||||
|
label: value.display,
|
||||||
|
time: value.moment - firstMoment,
|
||||||
|
color:
|
||||||
|
Object.entries(presetColors).find(([k, _]) => k === value.color)?.[1] ??
|
||||||
|
value.color,
|
||||||
|
key: value.key,
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.props.canSetCurrent && (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => this.props.onClick(this.state.selected)}
|
||||||
|
disabled={this.state.selected === this.props.timeline.current}>
|
||||||
|
Set as current
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<MarkerTimeline
|
||||||
|
points={points}
|
||||||
|
onClick={(ids) => this.setState({selected: ids[0]})}
|
||||||
|
maxGap={50}
|
||||||
|
selected={this.state.selected}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<DataInspector
|
||||||
|
data={
|
||||||
|
this.props.timeline.time.find(
|
||||||
|
(value) => value.key === this.state.selected,
|
||||||
|
)?.properties ?? {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user