From fa74ebba6877e4534386b20b9a9558bda92a9a12 Mon Sep 17 00:00:00 2001 From: Fabio Carballo Date: Mon, 27 Feb 2023 12:19:37 -0800 Subject: [PATCH] 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 (https://github.com/facebook/flipper/commit/b31f8c8755a04b34091ae30a86a9110e4b51a2a5). 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 --- .../plugins/inspector/InspectorValue.java | 88 +++++++++++++++++++ .../src/ui/data-inspector/DataDescription.tsx | 20 +++++ .../TimelineDataDescription.tsx | 88 +++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 desktop/flipper-plugin/src/ui/data-inspector/TimelineDataDescription.tsx diff --git a/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorValue.java b/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorValue.java index 42f4ab15d..81555bf4c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorValue.java +++ b/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorValue.java @@ -9,7 +9,14 @@ package com.facebook.flipper.plugins.inspector; import com.facebook.flipper.core.FlipperObject; 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 org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public class InspectorValue implements FlipperValue { @@ -30,6 +37,7 @@ public class InspectorValue implements FlipperValue { public static final Type Enum = new Type<>("enum"); public static final Type Color = new Type<>("color"); public static final Type Picker = new Type<>("picker"); + public static final Type Timeline = new Type<>("timeline"); private final String mName; @@ -108,4 +116,84 @@ public class InspectorValue implements FlipperValue { 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 time; + public final String current; + + public Timeline(List time, String current) { + Collections.sort( + time, + new Comparator() { + @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 properties; + + public TimePoint( + String key, long moment, String display, String color, Map 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(); + } + } + } } diff --git a/desktop/flipper-plugin/src/ui/data-inspector/DataDescription.tsx b/desktop/flipper-plugin/src/ui/data-inspector/DataDescription.tsx index fd46feab7..f72d3cb12 100644 --- a/desktop/flipper-plugin/src/ui/data-inspector/DataDescription.tsx +++ b/desktop/flipper-plugin/src/ui/data-inspector/DataDescription.tsx @@ -15,6 +15,7 @@ import {SketchPicker, CompactPicker} from 'react-color'; import React, {KeyboardEvent} from 'react'; import {HighlightContext} from '../Highlight'; import {parseColor} from '../../utils/parseColor'; +import {TimelineDataDescription} from './TimelineDataDescription'; import {theme} from '../theme'; import {EditOutlined} from '@ant-design/icons'; // This import is OK since it is a type-only import @@ -551,6 +552,25 @@ class DataDescriptionContainer extends PureComponent<{ const highlighter = this.context; switch (type) { + case 'timeline': { + return ( + <> + { + this.props.commit({ + value: id, + keep: true, + clear: false, + set: true, + }); + }} + /> + + ); + } + case 'number': return {+val}; diff --git a/desktop/flipper-plugin/src/ui/data-inspector/TimelineDataDescription.tsx b/desktop/flipper-plugin/src/ui/data-inspector/TimelineDataDescription.tsx new file mode 100644 index 000000000..c3bc1c3f8 --- /dev/null +++ b/desktop/flipper-plugin/src/ui/data-inspector/TimelineDataDescription.tsx @@ -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 { + 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 && ( +
+ +
+ )} +
+ this.setState({selected: ids[0]})} + maxGap={50} + selected={this.state.selected} + /> +
+
+ value.key === this.state.selected, + )?.properties ?? {} + } + /> +
+ + ); + } +}