@samitouri / QOS-React / commits / 01ad9f1da6

Implemented ranked chart

Brian Vaughn committed Mar 16, 2019 at 14:06 UTC 01ad9f1da6d6db997b1b2c865b2eb36de577538d
16 files changed +484 -132
src/devtools/ProfilingCache.js
+99 -76
@@ -6,6 +6,10 @@ import {
6 getCommitTree,
7 invalidateCommitTrees,
8 } from 'src/devtools/views/Profiler/CommitTreeBuilder';
9 +import {
10 + getChartData as getRankedChartData,
11 + invalidateChartData as invalidateRankedChartData,
12 +} from 'src/devtools/views/Profiler/RankedChartBuilder';
13
14 import type { Resource } from './cache';
15 import type { Bridge } from '../types';
@@ -15,9 +19,10 @@ import type {
19 } from 'src/backend/types';
20 import type {
21 CommitDetails as CommitDetailsFrontend,
18 - CommitTree,
22 + CommitTree as CommitTreeFrontend,
23 ProfilingSummary as ProfilingSummaryFrontend,
24 } from 'src/devtools/views/Profiler/types';
25 +import type { ChartData as RankedChartData } from 'src/devtools/views/Profiler/RankedChartBuilder';
26
27 type CommitDetailsParams = {|
28 commitIndex: number,
@@ -25,7 +30,7 @@ type CommitDetailsParams = {|
30 rendererID: number,
31 |};
32
28 -type CommitTreeParams = {|
33 +type GetCommitTreeParams = {|
34 commitIndex: number,
35 profilingSummary: ProfilingSummaryFrontend,
36 rendererID: number,
@@ -38,6 +43,9 @@ type ProfilingSummaryParams = {|
43 |};
44
45 export default class ProfilingCache {
46 + _bridge: Bridge;
47 + _store: Store;
48 +
49 _pendingCommitDetailsMap: Map<
50 string,
51 (commitDetails: CommitDetailsFrontend) => void
@@ -48,90 +56,105 @@ export default class ProfilingCache {
56 (profilingSummary: ProfilingSummaryFrontend) => void
57 > = new Map();
58
51 - CommitDetails: Resource<CommitDetailsParams, CommitDetailsFrontend>;
52 - CommitTree: Resource<CommitTreeParams, CommitTree>;
53 - ProfilingSummary: Resource<ProfilingSummaryParams, ProfilingSummaryFrontend>;
59 + CommitDetails: Resource<
60 + CommitDetailsParams,
61 + CommitDetailsFrontend
62 + > = createResource(
63 + ({ commitIndex, rendererID, rootID }: CommitDetailsParams) => {
64 + return new Promise(resolve => {
65 + if (!this._store.profilingOperations.has(rootID)) {
66 + // If no profiling data was recorded for this root, skip the round trip.
67 + resolve({
68 + actualDurations: new Map(),
69 + interactions: [],
70 + });
71 + } else {
72 + this._pendingCommitDetailsMap.set(
73 + `${rootID}-${commitIndex}`,
74 + resolve
75 + );
76 + this._bridge.send('getCommitDetails', {
77 + commitIndex,
78 + rendererID,
79 + rootID,
80 + });
81 + }
82 + });
83 + },
84 + ({ commitIndex, rendererID, rootID }: CommitDetailsParams) =>
85 + `${rootID}-${commitIndex}`
86 + );
87 +
88 + ProfilingSummary: Resource<
89 + ProfilingSummaryParams,
90 + ProfilingSummaryFrontend
91 + > = createResource(
92 + ({ rendererID, rootID }: ProfilingSummaryParams) => {
93 + return new Promise(resolve => {
94 + if (!this._store.profilingOperations.has(rootID)) {
95 + // If no profiling data was recorded for this root, skip the round trip.
96 + resolve({
97 + commitDurations: [],
98 + commitTimes: [],
99 + initialTreeBaseDurations: new Map(),
100 + interactionCount: 0,
101 + });
102 + } else {
103 + this._pendingProfileSummaryMap.set(rootID, resolve);
104 + this._bridge.send('getProfilingSummary', { rendererID, rootID });
105 + }
106 + });
107 + },
108 + ({ rendererID, rootID }: ProfilingSummaryParams) => rootID
109 + );
110
111 constructor(bridge: Bridge, store: Store) {
56 - this.CommitDetails = createResource(
57 - ({ commitIndex, rendererID, rootID }: CommitDetailsParams) => {
58 - return new Promise(resolve => {
59 - if (!store.profilingOperations.has(rootID)) {
60 - // If no profiling data was recorded for this root, skip the round trip.
61 - resolve({
62 - actualDurations: new Map(),
63 - interactions: [],
64 - });
65 - } else {
66 - this._pendingCommitDetailsMap.set(
67 - `${rootID}-${commitIndex}`,
68 - resolve
69 - );
70 - bridge.send('getCommitDetails', {
71 - commitIndex,
72 - rendererID,
73 - rootID,
74 - });
75 - }
76 - });
77 - },
78 - ({ commitIndex, rendererID, rootID }: CommitDetailsParams) =>
79 - `${rootID}-${commitIndex}`
80 - );
81 -
82 - this.CommitTree = createResource(
83 - ({
84 - commitIndex,
85 - profilingSummary,
86 - rendererID,
87 - rootID,
88 - }: CommitTreeParams) =>
89 - new Promise(resolve =>
90 - resolve(
91 - getCommitTree({
92 - commitIndex,
93 - profilingSummary,
94 - rendererID,
95 - rootID,
96 - store,
97 - })
98 - )
99 - ),
100 - ({
101 - commitIndex,
102 - profilingSummary,
103 - rendererID,
104 - rootID,
105 - }: CommitTreeParams) => `${rootID}-${commitIndex}`
106 - );
107 -
108 - this.ProfilingSummary = createResource(
109 - ({ rendererID, rootID }: ProfilingSummaryParams) => {
110 - return new Promise(resolve => {
111 - if (!store.profilingOperations.has(rootID)) {
112 - // If no profiling data was recorded for this root, skip the round trip.
113 - resolve({
114 - commitDurations: [],
115 - commitTimes: [],
116 - initialTreeBaseDurations: new Map(),
117 - interactionCount: 0,
118 - });
119 - } else {
120 - this._pendingProfileSummaryMap.set(rootID, resolve);
121 - bridge.send('getProfilingSummary', { rendererID, rootID });
122 - }
123 - });
124 - },
125 - ({ rendererID, rootID }: ProfilingSummaryParams) => rootID
126 - );
112 + this._bridge = bridge;
113 + this._store = store;
114
115 bridge.addListener('commitDetails', this.onCommitDetails);
116 bridge.addListener('profilingSummary', this.onProfileSummary);
117 }
118
119 + getCommitTree = ({
120 + commitIndex,
121 + profilingSummary,
122 + rendererID,
123 + rootID,
124 + }: GetCommitTreeParams) =>
125 + getCommitTree({
126 + commitIndex,
127 + profilingSummary,
128 + rendererID,
129 + rootID,
130 + store: this._store,
131 + });
132 +
133 + getRankedChartData = ({
134 + commitDetails,
135 + commitIndex,
136 + commitTree,
137 + rootID,
138 + }: {|
139 + commitDetails: CommitDetailsFrontend,
140 + commitIndex: number,
141 + commitTree: CommitTreeFrontend,
142 + rootID: number,
143 + |}): RankedChartData =>
144 + getRankedChartData({
145 + commitDetails,
146 + commitIndex,
147 + commitTree,
148 + rootID,
149 + });
150 +
151 invalidate() {
152 + // Invalidate Susepnse caches.
153 invalidateResources();
154 +
155 + // Invalidate non-Suspense caches too.
156 invalidateCommitTrees();
157 + invalidateRankedChartData();
158
159 this._pendingCommitDetailsMap.clear();
160 this._pendingProfileSummaryMap.clear();
src/devtools/views/Profiler/ChartNode.css new
+31
@@ -0,0 +1,31 @@
1 +.Group {
2 + transition: all ease-in-out 250ms;
3 +}
4 +
5 +.Rect {
6 + cursor: pointer;
7 + stroke: var(--color-background);
8 + transition: all ease-in-out 250ms;
9 +}
10 +
11 +.ForeignObject {
12 + pointer-events: none;
13 + transition: all ease-in-out 250ms;
14 +}
15 +
16 +.Div {
17 + pointer-events: none;
18 + white-space: nowrap;
19 + text-overflow: ellipsis;
20 + overflow: hidden;
21 + font-size: var(--font-family-sans);
22 + font-family: var(--font-size-sans-normal);
23 + margin-left: 4px;
24 + margin-right: 4px;
25 + line-height: 1.5;
26 + padding: 0 0 0;
27 + font-weight: 400;
28 + color: var(--color-commit-gradient-text);
29 + text-align: left;
30 + transition: all ease-in-out 250ms;
31 +}
src/devtools/views/Profiler/ChartNode.js new
+67
@@ -0,0 +1,67 @@
1 +// @flow
2 +
3 +import React from 'react';
4 +import { textHeight } from './constants';
5 +
6 +import styles from './ChartNode.css';
7 +
8 +type Props = {|
9 + color: string,
10 + height: number,
11 + isDimmed?: boolean,
12 + label: string,
13 + onClick: Function,
14 + onDoubleClick?: Function,
15 + placeLabelAboveNode?: boolean,
16 + title: string,
17 + width: number,
18 + x: number,
19 + y: number,
20 +|};
21 +
22 +const minWidthToDisplay = 35;
23 +
24 +export default function ChartNode({
25 + color,
26 + height,
27 + isDimmed = false,
28 + label,
29 + onClick,
30 + onDoubleClick,
31 + title,
32 + width,
33 + x,
34 + y,
35 +}: Props) {
36 + return (
37 + <g className={styles.Group} transform={`translate(${x},${y})`}>
38 + <title>{title}</title>
39 + <rect
40 + width={width}
41 + height={height}
42 + fill={color}
43 + onClick={onClick}
44 + onDoubleClick={onDoubleClick}
45 + className={styles.Rect}
46 + style={{
47 + opacity: isDimmed ? 0.5 : 1,
48 + }}
49 + />
50 + {width >= minWidthToDisplay && (
51 + <foreignObject
52 + width={width}
53 + height={height}
54 + className={styles.ForeignObject}
55 + style={{
56 + paddingLeft: x < 0 ? -x : 0,
57 + opacity: isDimmed ? 0.75 : 1,
58 + display: width < minWidthToDisplay ? 'none' : 'block',
59 + }}
60 + y={height < textHeight ? -textHeight : 0}
61 + >
62 + <div className={styles.Div}>{label}</div>
63 + </foreignObject>
64 + )}
65 + </g>
66 + );
67 +}
src/devtools/views/Profiler/CommitFlamegraph.js
+1 -1
@@ -24,7 +24,7 @@ export default function CommitFlamegraph(_: {||}) {
24 rootID: ((rootID: any): number),
25 });
26
27 - const commitTree = profilingCache.CommitTree.read({
27 + const commitTree = profilingCache.getCommitTree({
28 commitIndex: ((selectedCommitIndex: any): number),
29 profilingSummary,
30 rendererID: ((rendererID: any): number),
src/devtools/views/Profiler/CommitRanked.css
+5
@@ -0,0 +1,5 @@
1 +.Container {
2 + width: 100%;
3 + height: 100%;
4 + padding: 0.5rem;
5 +}
src/devtools/views/Profiler/CommitRanked.js
+94 -55
@@ -1,18 +1,55 @@
1 // @flow
2
3 -import React, { useContext } from 'react';
3 +import React, { useCallback, useContext, useMemo } from 'react';
4 +import AutoSizer from 'react-virtualized-auto-sizer';
5 +import { FixedSizeList } from 'react-window';
6 import { ProfilerContext } from './ProfilerContext';
5 -import { calculateSelfDuration } from './utils';
7 +import NoCommitData from './NoCommitData';
8 +import CommitRankedListItem from './CommitRankedListItem';
9 +import { barHeight } from './constants';
10 +import { scale } from './utils';
11 import { StoreContext } from '../context';
12
13 import styles from './CommitRanked.css';
14
10 -import type { CommitDetails, CommitTree, Node } from './types';
15 +import type { ChartData } from './RankedChartBuilder';
16
12 -export default function CommitRanked(_: {||}) {
13 - const { rendererID, rootID, selectedCommitIndex } = useContext(
14 - ProfilerContext
17 +export type ItemData = {|
18 + chartData: ChartData,
19 + scaleX: (value: number, fallbackValue: number) => number,
20 + selectedFiberID: number | null,
21 + selectedFiberIndex: number,
22 + selectFiber: (id: number | null) => void,
23 + width: number,
24 +|};
25 +
26 +export default function CommitRankedAutoSizer(_: {||}) {
27 + const { selectFiber } = useContext(ProfilerContext);
28 + const deselectCurrentFiber = useCallback(
29 + event => {
30 + event.stopPropagation();
31 + selectFiber(null);
32 + },
33 + [selectFiber]
34 + );
35 +
36 + return (
37 + <div className={styles.Container} onClick={deselectCurrentFiber}>
38 + <AutoSizer>
39 + {({ height, width }) => <CommitRanked height={height} width={width} />}
40 + </AutoSizer>
41 + </div>
42 );
43 +}
44 +
45 +function CommitRanked({ height, width }: {| height: number, width: number |}) {
46 + const {
47 + rendererID,
48 + rootID,
49 + selectedCommitIndex,
50 + selectedFiberID,
51 + selectFiber,
52 + } = useContext(ProfilerContext);
53
54 const { profilingCache } = useContext(StoreContext);
55
@@ -27,64 +64,66 @@ export default function CommitRanked(_: {||}) {
64 rootID: ((rootID: any): number),
65 });
66
30 - const commitTree = profilingCache.CommitTree.read({
67 + const commitTree = profilingCache.getCommitTree({
68 commitIndex: ((selectedCommitIndex: any): number),
69 profilingSummary,
70 rendererID: ((rendererID: any): number),
71 rootID: ((rootID: any): number),
72 });
73
37 - const chartData = generateChartData(commitTree, commitDetails);
38 -
39 - return 'Coming soon: Ranked';
40 -}
41 -
42 -type ChartNode = {|
43 - id: number,
44 - label: string,
45 - name: string,
46 - title: string,
47 - value: number,
48 -|};
49 -
50 -type ChartData = {|
51 - maxValue: number,
52 - nodes: Array<ChartNode>,
53 -|};
74 + const chartData = profilingCache.getRankedChartData({
75 + commitDetails,
76 + commitIndex: ((selectedCommitIndex: any): number),
77 + commitTree,
78 + rootID: ((rootID: any): number),
79 + });
80
55 -const generateChartData = (
56 - commitTree: CommitTree,
57 - commitDetails: CommitDetails
58 -): ChartData => {
59 - const { nodes } = commitTree;
81 + const selectedFiberIndex = useMemo(
82 + () => getNodeIndex(chartData, selectedFiberID),
83 + [chartData, selectedFiberID]
84 + );
85
61 - let maxSelfDuration = 0;
86 + const itemData = useMemo<ItemData>(
87 + () => ({
88 + chartData,
89 + scaleX: scale(0, chartData.nodes[selectedFiberIndex].value, 0, width),
90 + selectedFiberID,
91 + selectedFiberIndex,
92 + selectFiber,
93 + width,
94 + }),
95 + [chartData, selectedFiberID, selectedFiberIndex, selectFiber, width]
96 + );
97
63 - const chartNodes: Array<ChartNode> = [];
64 - commitDetails.actualDurations.forEach((actualDuration, id) => {
65 - const node = ((nodes.get(id): any): Node);
98 + // If a commit contains no fibers with an actualDuration > 0,
99 + // Display a fallback message.
100 + if (chartData.nodes.length === 0) {
101 + return <NoCommitData />;
102 + }
103 +
104 + return (
105 + <FixedSizeList
106 + height={height}
107 + innerTagName="svg"
108 + itemCount={chartData.nodes.length}
109 + itemData={itemData}
110 + itemSize={barHeight}
111 + width={width}
112 + >
113 + {CommitRankedListItem}
114 + </FixedSizeList>
115 + );
116 +}
117
67 - // Don't show the root node in this chart.
68 - if (node.parentID === 0) {
69 - return;
118 +const getNodeIndex = (chartData: ChartData, id: number | null): number => {
119 + if (id === null) {
120 + return 0;
121 + }
122 + const { nodes } = chartData;
123 + for (let index = 0; index < nodes.length; index++) {
124 + if (nodes[index].id === id) {
125 + return index;
126 }
71 -
72 - const selfDuration = calculateSelfDuration(id, commitTree, commitDetails);
73 - maxSelfDuration = Math.max(maxSelfDuration, selfDuration);
74 -
75 - const name = node.displayName || 'Unknown';
76 - const label = `${name} (${selfDuration.toFixed(1)}ms)`;
77 - chartNodes.push({
78 - id,
79 - label,
80 - name,
81 - title: label,
82 - value: selfDuration,
83 - });
84 - });
85 -
86 - return {
87 - maxValue: maxSelfDuration,
88 - nodes: chartNodes.sort((a, b) => b.value - a.value),
89 - };
127 + }
128 + return 0;
129 };
src/devtools/views/Profiler/CommitRankedListItem.js new
+51
@@ -0,0 +1,51 @@
1 +// @flow
2 +
3 +import React, { useCallback } from 'react';
4 +import { barHeight, minBarWidth } from './constants';
5 +import { getGradientColor } from './utils';
6 +import ChartNode from './ChartNode';
7 +
8 +import type { ItemData } from './CommitRanked';
9 +
10 +export default function CommitRankedListItem({
11 + data,
12 + index,
13 + style,
14 +}: {
15 + data: ItemData,
16 + index: number,
17 + style: Object,
18 +}) {
19 + const { chartData, scaleX, selectedFiberIndex, selectFiber, width } = data;
20 +
21 + const node = chartData.nodes[index];
22 +
23 + const handleClick = useCallback(
24 + event => {
25 + event.stopPropagation();
26 + selectFiber(node.id);
27 + },
28 + [node, selectFiber]
29 + );
30 +
31 + // List items are absolutely positioned using the CSS "top" attribute.
32 + // The "left" value will always be 0.
33 + // Since height is fixed, and width is based on the node's duration,
34 + // We can ignore those values as well.
35 + const top = parseInt(style.top, 10);
36 +
37 + return (
38 + <ChartNode
39 + color={getGradientColor(node.value / chartData.maxValue)}
40 + height={barHeight}
41 + isDimmed={index < selectedFiberIndex}
42 + key={node.id}
43 + label={node.label}
44 + onClick={handleClick}
45 + title={node.title}
46 + width={Math.max(minBarWidth, scaleX(node.value, width))}
47 + x={0}
48 + y={top}
49 + />
50 + );
51 +}
src/devtools/views/Profiler/NoCommitData.css new
+11
@@ -0,0 +1,11 @@
1 +.NoCommitData {
2 + width: 100%;
3 + height: 100%;
4 + display: flex;
5 + align-items: center;
6 + justify-content: center;
7 +}
8 +
9 +.Header {
10 + font-size: var(--font-size-sans-large);
11 +}
src/devtools/views/Profiler/NoCommitData.js new
+20
@@ -0,0 +1,20 @@
1 +// @flow
2 +
3 +import React from 'react';
4 +
5 +import styles from './NoCommitData.css';
6 +
7 +export default function NoCommitData() {
8 + return (
9 + <div className={styles.NoCommitData}>
10 + <p className={styles.Header}>
11 + There is no timing data to display for the currently selected commit.
12 + </p>
13 + <p>
14 + This can indicate that a render occurred too quickly for the timing API
15 + to measure. Try selecting another commit in the upper, right-hand
16 + corner.
17 + </p>
18 + </div>
19 + );
20 +}
src/devtools/views/Profiler/ProfilerContext.js
+12
@@ -44,6 +44,10 @@ type Context = {|
44 // It impacts the flame graph and ranked charts.
45 selectedCommitIndex: number | null,
46 setSelectedCommitIndex: (value: number | null) => void,
47 +
48 + // Which fiber is currently selected in the Ranked or Flamegraph charts?
49 + selectedFiberID: number | null,
50 + selectFiber: (id: number | null) => void,
51 |};
52
53 const ProfilerContext = createContext<Context>(((null: any): Context));
@@ -118,6 +122,8 @@ function ProfilerContextController({ children }: Props) {
122 null
123 );
124
125 + const [selectedFiberID, selectFiber] = useState<number | null>(null);
126 +
127 const value = useMemo(
128 () => ({
129 hasProfilingData,
@@ -136,6 +142,9 @@ function ProfilerContextController({ children }: Props) {
142
143 selectedCommitIndex,
144 setSelectedCommitIndex,
145 +
146 + selectedFiberID,
147 + selectFiber,
148 }),
149 [
150 hasProfilingData,
@@ -154,6 +163,9 @@ function ProfilerContextController({ children }: Props) {
163
164 selectedCommitIndex,
165 setSelectedCommitIndex,
166 +
167 + selectedFiberID,
168 + selectFiber,
169 ]
170 );
171
src/devtools/views/Profiler/RankedChartBuilder.js new
+78
@@ -0,0 +1,78 @@
1 +// @flow
2 +
3 +import { calculateSelfDuration } from './utils';
4 +
5 +import type { CommitDetails, CommitTree, Node } from './types';
6 +
7 +export type ChartNode = {|
8 + id: number,
9 + label: string,
10 + name: string,
11 + title: string,
12 + value: number,
13 +|};
14 +
15 +export type ChartData = {|
16 + maxValue: number,
17 + nodes: Array<ChartNode>,
18 +|};
19 +
20 +const cachedChartData: Map<string, ChartData> = new Map();
21 +
22 +export function getChartData({
23 + commitDetails,
24 + commitIndex,
25 + commitTree,
26 + rootID,
27 +}: {|
28 + commitDetails: CommitDetails,
29 + commitIndex: number,
30 + commitTree: CommitTree,
31 + rootID: number,
32 +|}): ChartData {
33 + const key = `${rootID}-${commitIndex}`;
34 +
35 + if (cachedChartData.has(key)) {
36 + return ((cachedChartData.get(key): any): ChartData);
37 + }
38 +
39 + const { nodes } = commitTree;
40 +
41 + let maxSelfDuration = 0;
42 +
43 + const chartNodes: Array<ChartNode> = [];
44 + commitDetails.actualDurations.forEach((actualDuration, id) => {
45 + const node = ((nodes.get(id): any): Node);
46 +
47 + // Don't show the root node in this chart.
48 + if (node.parentID === 0) {
49 + return;
50 + }
51 +
52 + const selfDuration = calculateSelfDuration(id, commitTree, commitDetails);
53 + maxSelfDuration = Math.max(maxSelfDuration, selfDuration);
54 +
55 + const name = node.displayName || 'Unknown';
56 + const label = `${name} (${selfDuration.toFixed(1)}ms)`;
57 + chartNodes.push({
58 + id,
59 + label,
60 + name,
61 + title: label,
62 + value: selfDuration,
63 + });
64 + });
65 +
66 + const chartData = {
67 + maxValue: maxSelfDuration,
68 + nodes: chartNodes.sort((a, b) => b.value - a.value),
69 + };
70 +
71 + cachedChartData.set(key, chartData);
72 +
73 + return chartData;
74 +}
75 +
76 +export function invalidateChartData(): void {
77 + cachedChartData.clear();
78 +}
src/devtools/views/Profiler/SnapshotSelector.css
+1
@@ -21,6 +21,7 @@
21 flex: 0 0 auto;
22 white-space: nowrap;
23 font-family: var(--font-family-monospace);
24 + margin-left: 0.25rem;
25 }
26
27 .NoCommits {
src/devtools/views/Profiler/constants.js
+1
@@ -1,5 +1,6 @@
1 // @flow
2
3 +export const barHeight = 20;
4 export const maxBarWidth = 30;
5 export const minBarHeight = 5;
6 export const minBarWidth = 5;
src/devtools/views/Profiler/utils.js
+10
@@ -58,3 +58,13 @@ export const formatPercentage = (percentage: number) =>
58 Math.round(percentage * 100);
59 export const formatTime = (timestamp: number) =>
60 Math.round(Math.round(timestamp) / 100) / 10;
61 +
62 +export const scale = (
63 + minValue: number,
64 + maxValue: number,
65 + minRange: number,
66 + maxRange: number
67 +) => (value: number, fallbackValue: number) =>
68 + maxValue - minValue === 0
69 + ? fallbackValue
70 + : ((value - minValue) / (maxValue - minValue)) * (maxRange - minRange);
src/devtools/views/Settings/SettingsContext.js
+1
@@ -145,6 +145,7 @@ function updateThemeVariables(theme: Theme): void {
145 updateStyleHelper(theme, 'color-commit-gradient-7');
146 updateStyleHelper(theme, 'color-commit-gradient-8');
147 updateStyleHelper(theme, 'color-commit-gradient-9');
148 + updateStyleHelper(theme, 'color-commit-gradient-text');
149 updateStyleHelper(theme, 'color-component-name');
150 updateStyleHelper(theme, 'color-component-name-inverted');
151 updateStyleHelper(theme, 'color-dim');
src/devtools/views/root.css
+2
@@ -27,6 +27,7 @@
27 --light-color-commit-gradient-7: #dfba57;
28 --light-color-commit-gradient-8: #efbb49;
29 --light-color-commit-gradient-9: #febc38;
30 + --light-color-commit-gradient-text: #000000;
31 --light-color-component-name: #8155cb;
32 --light-color-component-name-inverted: #ffffff;
33 --light-color-dim: #777d88;
@@ -68,6 +69,7 @@
69 --dark-color-commit-gradient-7: #dfba57;
70 --dark-color-commit-gradient-8: #efbb49;
71 --dark-color-commit-gradient-9: #febc38;
72 + --dark-color-commit-gradient-text: #000000;
73 --dark-color-component-name: #61dafb;
74 --dark-color-component-name-inverted: ##282828;
75 --dark-color-dim: #8f949d;