@parca/profile 0.16.22 → 0.16.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +44 -45
  2. package/dist/Callgraph/Edge/index.d.ts +1 -0
  3. package/dist/Callgraph/Node/index.d.ts +2 -1
  4. package/dist/Callgraph/index.d.ts +1 -0
  5. package/dist/GraphTooltip/index.d.ts +1 -0
  6. package/dist/GraphTooltip/index.js +51 -15
  7. package/dist/IcicleGraph.d.ts +1 -0
  8. package/dist/IcicleGraph.js +23 -4
  9. package/dist/MatchersInput/index.d.ts +1 -0
  10. package/dist/MetricsCircle/index.d.ts +1 -0
  11. package/dist/MetricsGraph/index.d.ts +1 -0
  12. package/dist/MetricsGraph/index.js +5 -27
  13. package/dist/MetricsSeries/index.d.ts +1 -0
  14. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -0
  15. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -0
  16. package/dist/ProfileExplorer/index.d.ts +1 -0
  17. package/dist/ProfileIcicleGraph.d.ts +1 -0
  18. package/dist/ProfileMetricsGraph/index.d.ts +1 -0
  19. package/dist/ProfileSelector/CompareButton.d.ts +1 -0
  20. package/dist/ProfileSelector/MergeButton.d.ts +1 -0
  21. package/dist/ProfileSelector/index.d.ts +1 -0
  22. package/dist/ProfileSource.d.ts +1 -0
  23. package/dist/ProfileTypeSelector/index.d.ts +1 -0
  24. package/dist/ProfileView.d.ts +1 -0
  25. package/dist/ProfileViewWithData.d.ts +1 -0
  26. package/dist/TopTable.d.ts +2 -1
  27. package/dist/components/DiffLegend.d.ts +1 -0
  28. package/dist/components/ProfileShareButton/ResultBox.d.ts +1 -0
  29. package/dist/components/ProfileShareButton/index.d.ts +1 -0
  30. package/dist/styles.css +1 -1
  31. package/package.json +10 -9
  32. package/src/GraphTooltip/index.tsx +116 -21
  33. package/src/IcicleGraph.tsx +25 -5
  34. package/src/MetricsGraph/index.tsx +6 -31
package/CHANGELOG.md CHANGED
@@ -3,137 +3,136 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [0.16.22](https://github.com/parca-dev/parca/compare/ui-v0.16.20...ui-v0.16.22) (2022-09-27)
6
+ ## [0.16.49](https://github.com/parca-dev/parca/compare/ui-v0.16.48...ui-v0.16.49) (2022-10-17)
7
7
 
8
8
  **Note:** Version bump only for package @parca/profile
9
9
 
10
+ ## [0.16.48](https://github.com/parca-dev/parca/compare/ui-v0.16.47...ui-v0.16.48) (2022-10-17)
10
11
 
12
+ **Note:** Version bump only for package @parca/profile
11
13
 
14
+ ## [0.16.44](https://github.com/parca-dev/parca/compare/ui-v0.16.43...ui-v0.16.44) (2022-10-13)
12
15
 
16
+ **Note:** Version bump only for package @parca/profile
13
17
 
14
- ## [0.16.21](https://github.com/parca-dev/parca/compare/ui-v0.16.20...ui-v0.16.21) (2022-09-27)
18
+ ## [0.16.43](https://github.com/parca-dev/parca/compare/ui-v0.16.42...ui-v0.16.43) (2022-10-13)
15
19
 
16
20
  **Note:** Version bump only for package @parca/profile
17
21
 
22
+ ## [0.16.42](https://github.com/parca-dev/parca/compare/ui-v0.16.41...ui-v0.16.42) (2022-10-13)
18
23
 
24
+ **Note:** Version bump only for package @parca/profile
19
25
 
26
+ ## [0.16.40](https://github.com/parca-dev/parca/compare/ui-v0.16.39...ui-v0.16.40) (2022-10-10)
20
27
 
28
+ **Note:** Version bump only for package @parca/profile
21
29
 
22
- ## [0.16.20](https://github.com/parca-dev/parca/compare/ui-v0.16.19...ui-v0.16.20) (2022-09-27)
30
+ ## [0.16.39](https://github.com/parca-dev/parca/compare/ui-v0.16.38...ui-v0.16.39) (2022-10-10)
23
31
 
24
32
  **Note:** Version bump only for package @parca/profile
25
33
 
34
+ ## [0.16.38](https://github.com/parca-dev/parca/compare/ui-v0.16.37...ui-v0.16.38) (2022-10-10)
26
35
 
36
+ ## [0.16.32](https://github.com/parca-dev/parca/compare/ui-v0.16.30...ui-v0.16.32) (2022-10-06)
27
37
 
38
+ ## [0.16.30](https://github.com/parca-dev/parca/compare/ui-v0.16.27...ui-v0.16.30) (2022-10-01)
28
39
 
29
-
30
- ## [0.16.19](https://github.com/parca-dev/parca/compare/ui-v0.16.18...ui-v0.16.19) (2022-09-26)
40
+ ## 0.16.27 (2022-09-29)
31
41
 
32
42
  **Note:** Version bump only for package @parca/profile
33
43
 
44
+ ## [0.16.32](https://github.com/parca-dev/parca/compare/ui-v0.16.31...ui-v0.16.32) (2022-10-06)
34
45
 
46
+ **Note:** Version bump only for package @parca/profile
35
47
 
48
+ ## [0.16.28](https://github.com/parca-dev/parca/compare/ui-v0.16.27...ui-v0.16.28) (2022-09-30)
36
49
 
50
+ **Note:** Version bump only for package @parca/profile
37
51
 
38
- ## [0.16.16](https://github.com/parca-dev/parca/compare/ui-v0.16.15...ui-v0.16.16) (2022-09-26)
52
+ ## [0.16.24](https://github.com/parca-dev/parca/compare/ui-v0.16.23...ui-v0.16.24) (2022-09-27)
39
53
 
40
- **Note:** Version bump only for package @parca/profile
54
+ ## [0.16.20](https://github.com/parca-dev/parca/compare/ui-v0.16.19...ui-v0.16.20) (2022-09-27)
41
55
 
56
+ ## [0.16.19](https://github.com/parca-dev/parca/compare/ui-v0.16.18...ui-v0.16.19) (2022-09-26)
42
57
 
58
+ ## [0.16.18](https://github.com/parca-dev/parca/compare/ui-v0.16.17...ui-v0.16.18) (2022-09-26)
43
59
 
60
+ ## [0.16.2](https://github.com/parca-dev/parca/compare/ui-v0.13.4...ui-v0.16.2) (2022-09-14)
44
61
 
62
+ ## [0.13.4](https://github.com/parca-dev/parca/compare/ui-v0.13.3...ui-v0.13.4) (2022-06-07)
45
63
 
46
- # [0.16.0](https://github.com/parca-dev/parca/compare/ui-v0.15.26...ui-v0.16.0) (2022-09-13)
64
+ **Note:** Version bump only for package @parca/profile
65
+
66
+ ## [0.16.23](https://github.com/parca-dev/parca/compare/ui-v0.16.20...ui-v0.16.23) (2022-09-27)
47
67
 
48
68
  **Note:** Version bump only for package @parca/profile
49
69
 
70
+ ## [0.16.22](https://github.com/parca-dev/parca/compare/ui-v0.16.20...ui-v0.16.22) (2022-09-27)
50
71
 
72
+ **Note:** Version bump only for package @parca/profile
51
73
 
74
+ ## [0.16.21](https://github.com/parca-dev/parca/compare/ui-v0.16.20...ui-v0.16.21) (2022-09-27)
52
75
 
76
+ **Note:** Version bump only for package @parca/profile
53
77
 
54
- ## [0.15.25](https://github.com/parca-dev/parca/compare/ui-v0.15.24...ui-v0.15.25) (2022-09-13)
78
+ ## [0.16.20](https://github.com/parca-dev/parca/compare/ui-v0.16.19...ui-v0.16.20) (2022-09-27)
55
79
 
56
80
  **Note:** Version bump only for package @parca/profile
57
81
 
82
+ ## [0.16.19](https://github.com/parca-dev/parca/compare/ui-v0.16.18...ui-v0.16.19) (2022-09-26)
58
83
 
84
+ **Note:** Version bump only for package @parca/profile
59
85
 
86
+ ## [0.16.16](https://github.com/parca-dev/parca/compare/ui-v0.16.15...ui-v0.16.16) (2022-09-26)
60
87
 
88
+ **Note:** Version bump only for package @parca/profile
61
89
 
62
- ## [0.15.24](https://github.com/parca-dev/parca/compare/ui-v0.15.23...ui-v0.15.24) (2022-09-13)
90
+ # [0.16.0](https://github.com/parca-dev/parca/compare/ui-v0.15.26...ui-v0.16.0) (2022-09-13)
63
91
 
92
+ **Note:** Version bump only for package @parca/profile
64
93
 
94
+ ## [0.15.25](https://github.com/parca-dev/parca/compare/ui-v0.15.24...ui-v0.15.25) (2022-09-13)
65
95
 
66
- ## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.19) (2022-09-08)
96
+ **Note:** Version bump only for package @parca/profile
67
97
 
98
+ ## [0.15.24](https://github.com/parca-dev/parca/compare/ui-v0.15.23...ui-v0.15.24) (2022-09-13)
68
99
 
100
+ ## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.19) (2022-09-08)
69
101
 
70
102
  ## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.17) (2022-09-07)
71
103
 
72
-
73
104
  ### Bug Fixes
74
105
 
75
- * **ui:** type all the things (string mode) ([e91ac9d](https://github.com/parca-dev/parca/commit/e91ac9deb8a200ed3c9a9c5d81ae71e13d49466e))
76
-
77
-
106
+ - **ui:** type all the things (string mode) ([e91ac9d](https://github.com/parca-dev/parca/commit/e91ac9deb8a200ed3c9a9c5d81ae71e13d49466e))
78
107
 
79
108
  ## 0.15.10 (2022-08-30)
80
109
 
81
-
82
-
83
-
84
-
85
110
  ## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.18...ui-v0.15.19) (2022-09-08)
86
111
 
87
-
88
-
89
112
  ## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.17) (2022-09-07)
90
113
 
91
-
92
-
93
114
  ## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.8...ui-v0.15.10) (2022-08-30)
94
115
 
95
116
  **Note:** Version bump only for package @parca/profile
96
117
 
97
-
98
-
99
-
100
-
101
118
  ## [0.15.18](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.18) (2022-09-08)
102
119
 
103
-
104
-
105
120
  ## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.8...ui-v0.15.10) (2022-08-30)
106
121
 
107
122
  **Note:** Version bump only for package @parca/profile
108
123
 
109
-
110
-
111
-
112
-
113
124
  ## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.16...ui-v0.15.17) (2022-09-07)
114
125
 
115
-
116
-
117
126
  ## [0.15.11](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.11) (2022-09-01)
118
127
 
119
-
120
-
121
128
  ## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.2...ui-v0.15.10) (2022-08-30)
122
129
 
123
-
124
-
125
130
  ## [0.15.2](https://github.com/parca-dev/parca/compare/ui-v0.15.1...ui-v0.15.2) (2022-08-25)
126
131
 
127
-
128
-
129
132
  ## 0.15.1 (2022-08-25)
130
133
 
131
134
  **Note:** Version bump only for package @parca/profile
132
135
 
133
-
134
-
135
-
136
-
137
136
  ## [0.15.11](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.11) (2022-09-01)
138
137
 
139
138
  **Note:** Version bump only for package @parca/profile
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  export interface IEdge {
2
3
  source: number;
3
4
  target: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  export interface INode {
2
3
  id: number;
3
4
  x: number;
@@ -15,5 +16,5 @@ interface Props {
15
16
  setHoveredNode: (node: INode | null) => void;
16
17
  nodeRadius: number;
17
18
  }
18
- declare const Node: ({ node, hoveredNode, setHoveredNode, nodeRadius, }: Props) => JSX.Element;
19
+ declare const Node: ({ node, hoveredNode, setHoveredNode, nodeRadius: defaultRadius, }: Props) => JSX.Element;
19
20
  export default Node;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Callgraph as CallgraphType } from '@parca/client';
2
3
  export interface Props {
3
4
  graph: CallgraphType;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { CallgraphNode, FlamegraphRootNode } from '@parca/client';
2
3
  interface GraphTooltipProps {
3
4
  x: number;
@@ -21,10 +21,24 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  return t;
22
22
  };
23
23
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
- import { getLastItem, valueFormatter } from '@parca/functions';
25
- import { hexifyAddress } from '../';
24
+ // Copyright 2022 The Parca Authors
25
+ // Licensed under the Apache License, Version 2.0 (the "License");
26
+ // you may not use this file except in compliance with the License.
27
+ // You may obtain a copy of the License at
28
+ //
29
+ // http://www.apache.org/licenses/LICENSE-2.0
30
+ //
31
+ // Unless required by applicable law or agreed to in writing, software
32
+ // distributed under the License is distributed on an "AS IS" BASIS,
33
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34
+ // See the License for the specific language governing permissions and
35
+ // limitations under the License.
36
+ import { CopyToClipboard } from 'react-copy-to-clipboard';
26
37
  import { useState, useEffect } from 'react';
27
38
  import { usePopper } from 'react-popper';
39
+ import { getLastItem, valueFormatter } from '@parca/functions';
40
+ import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
41
+ import { hexifyAddress } from '../';
28
42
  var virtualElement = {
29
43
  getBoundingClientRect: function () {
30
44
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -55,21 +69,37 @@ function generateGetBoundingClientRect(contextElement, x, y) {
55
69
  };
56
70
  }
57
71
  var TooltipMetaInfo = function (_a) {
58
- var _b, _c, _d, _e, _f, _g, _h, _j;
59
- var hoveringNode = _a.hoveringNode;
72
+ var _b, _c, _d, _e;
73
+ var hoveringNode = _a.hoveringNode, onCopy = _a.onCopy;
60
74
  if (hoveringNode.meta === undefined)
61
75
  return _jsx(_Fragment, {});
76
+ var getTextForFile = function (hoveringNode) {
77
+ var _a, _b, _c, _d;
78
+ if (hoveringNode.meta === undefined)
79
+ return '<unknown>';
80
+ // @ts-expect-error
81
+ return "".concat(hoveringNode.meta.function.filename, " ").concat(((_a = hoveringNode.meta.line) === null || _a === void 0 ? void 0 : _a.line) !== undefined && ((_b = hoveringNode.meta.line) === null || _b === void 0 ? void 0 : _b.line) !== '0'
82
+ ? " +".concat(hoveringNode.meta.line.line.toString())
83
+ : "".concat(((_c = hoveringNode.meta.function) === null || _c === void 0 ? void 0 : _c.startLine) !== undefined &&
84
+ ((_d = hoveringNode.meta.function) === null || _d === void 0 ? void 0 : _d.startLine) !== '0'
85
+ ? " +".concat(hoveringNode.meta.function.startLine)
86
+ : ''));
87
+ };
62
88
  return (_jsxs(_Fragment, { children: [((_b = hoveringNode.meta.function) === null || _b === void 0 ? void 0 : _b.filename) !== undefined &&
63
- ((_c = hoveringNode.meta.function) === null || _c === void 0 ? void 0 : _c.filename) !== '' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "File" })), _jsxs("td", __assign({ className: "w-4/5 break-all" }, { children: [hoveringNode.meta.function.filename, ((_d = hoveringNode.meta.line) === null || _d === void 0 ? void 0 : _d.line) !== undefined && ((_e = hoveringNode.meta.line) === null || _e === void 0 ? void 0 : _e.line) !== '0'
64
- ? " +".concat(hoveringNode.meta.line.line.toString())
65
- : "".concat(((_f = hoveringNode.meta.function) === null || _f === void 0 ? void 0 : _f.startLine) !== undefined &&
66
- ((_g = hoveringNode.meta.function) === null || _g === void 0 ? void 0 : _g.startLine) !== '0'
67
- ? " +".concat(hoveringNode.meta.function.startLine)
68
- : '')] }))] })), ((_h = hoveringNode.meta.location) === null || _h === void 0 ? void 0 : _h.address) !== undefined &&
69
- ((_j = hoveringNode.meta.location) === null || _j === void 0 ? void 0 : _j.address) !== '0' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Address" })), _jsx("td", __assign({ className: "w-4/5 break-all" }, { children: ' 0x' + hoveringNode.meta.location.address.toString() }))] })), hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.file !== '' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Binary" })), _jsx("td", __assign({ className: "w-4/5 break-all" }, { children: getLastItem(hoveringNode.meta.mapping.file) }))] }))] }));
89
+ ((_c = hoveringNode.meta.function) === null || _c === void 0 ? void 0 : _c.filename) !== '' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "File" })), _jsx("td", __assign({ className: "w-4/5 break-all" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: getTextForFile(hoveringNode) }, { children: _jsx("button", __assign({ className: "cursor-pointer text-left" }, { children: getTextForFile(hoveringNode) })) })) }))] })), ((_d = hoveringNode.meta.location) === null || _d === void 0 ? void 0 : _d.address) !== undefined &&
90
+ ((_e = hoveringNode.meta.location) === null || _e === void 0 ? void 0 : _e.address) !== '0' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Address" })), _jsx("td", __assign({ className: "w-4/5 break-all" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: ' 0x' + hoveringNode.meta.location.address.toString() }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: ' 0x' + hoveringNode.meta.location.address.toString() })) })) }))] })), hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.file !== '' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Binary" })), _jsx("td", __assign({ className: "w-4/5 break-all" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hoveringNode.meta.mapping.file }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: getLastItem(hoveringNode.meta.mapping.file) })) })) }))] })), hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.buildId !== '' && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Build Id" })), _jsx("td", __assign({ className: "w-4/5 break-all" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hoveringNode.meta.mapping.buildId }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: getLastItem(hoveringNode.meta.mapping.buildId) })) })) }))] }))] }));
70
91
  };
92
+ var timeoutHandle = null;
71
93
  var GraphTooltipContent = function (_a) {
72
94
  var hoveringNode = _a.hoveringNode, unit = _a.unit, total = _a.total, isFixed = _a.isFixed;
95
+ var _b = useState(false), isCopied = _b[0], setIsCopied = _b[1];
96
+ var onCopy = function () {
97
+ setIsCopied(true);
98
+ if (timeoutHandle !== null) {
99
+ clearTimeout(timeoutHandle);
100
+ }
101
+ timeoutHandle = setTimeout(function () { return setIsCopied(false); }, 3000);
102
+ };
73
103
  var hoveringNodeCumulative = parseFloat(hoveringNode.cumulative);
74
104
  var diff = hoveringNode.diff === undefined ? 0 : parseFloat(hoveringNode.diff);
75
105
  var prevValue = hoveringNodeCumulative - diff;
@@ -78,10 +108,13 @@ var GraphTooltipContent = function (_a) {
78
108
  var diffValueText = diffSign + valueFormatter(diff, unit, 1);
79
109
  var diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
80
110
  var diffText = "".concat(diffValueText, " (").concat(diffPercentageText, ")");
81
- var metaRows = hoveringNode.meta === undefined ? _jsx(_Fragment, {}) : _jsx(TooltipMetaInfo, { hoveringNode: hoveringNode });
111
+ var metaRows = hoveringNode.meta === undefined ? (_jsx(_Fragment, {})) : (_jsx(TooltipMetaInfo, { onCopy: onCopy, hoveringNode: hoveringNode }));
112
+ var getTextForCumulative = function (hoveringNodeCumulative) {
113
+ return "".concat(valueFormatter(hoveringNodeCumulative, unit, 2), " (\n ").concat(((hoveringNodeCumulative * 100) / total).toFixed(2), "%)");
114
+ };
82
115
  return (_jsx("div", __assign({ className: "flex ".concat(isFixed ? 'w-full h-36' : '') }, { children: _jsx("div", __assign({ className: "m-auto w-full ".concat(isFixed ? 'w-full h-36' : '') }, { children: _jsx("div", __assign({ className: "border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-900 rounded-lg p-3 shadow-lg opacity-90", style: { borderWidth: 1 } }, { children: _jsx("div", __assign({ className: "flex flex-row" }, { children: _jsxs("div", __assign({ className: "ml-2 mr-6" }, { children: [_jsx("span", __assign({ className: "font-semibold break-all" }, { children: hoveringNode.meta === undefined ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: hoveringNode.meta.function !== undefined &&
83
- hoveringNode.meta.function.name !== '' ? (_jsx("p", { children: hoveringNode.meta.function.name })) : (_jsx(_Fragment, { children: hoveringNode.meta.location !== undefined &&
84
- parseInt(hoveringNode.meta.location.address, 10) !== 0 ? (_jsx("p", { children: hexifyAddress(hoveringNode.meta.location.address) })) : (_jsx("p", { children: "unknown" })) })) })) })), _jsx("span", __assign({ className: "text-gray-700 dark:text-gray-300 my-2" }, { children: _jsx("table", __assign({ className: "table-fixed" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Cumulative" })), _jsxs("td", __assign({ className: "w-4/5" }, { children: [valueFormatter(hoveringNodeCumulative, unit, 2), " (", ((hoveringNodeCumulative * 100) / total).toFixed(2), "%)"] }))] }), hoveringNode.diff !== undefined && diff !== 0 && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Diff" })), _jsx("td", __assign({ className: "w-4/5" }, { children: diffText }))] })), metaRows] }) })) }))] })) })) })) })) })));
116
+ hoveringNode.meta.function.name !== '' ? (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hoveringNode.meta.function.name }, { children: _jsx("button", __assign({ className: "cursor-pointer text-left" }, { children: hoveringNode.meta.function.name })) }))) : (_jsx(_Fragment, { children: hoveringNode.meta.location !== undefined &&
117
+ parseInt(hoveringNode.meta.location.address, 10) !== 0 ? (_jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: hexifyAddress(hoveringNode.meta.location.address) }, { children: _jsx("button", __assign({ className: "cursor-pointer text-left" }, { children: hexifyAddress(hoveringNode.meta.location.address) })) }))) : (_jsx("p", { children: "unknown" })) })) })) })), _jsx("span", __assign({ className: "text-gray-700 dark:text-gray-300 my-2" }, { children: _jsx("table", __assign({ className: "table-fixed" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Cumulative" })), _jsx("td", __assign({ className: "w-4/5" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: getTextForCumulative(hoveringNodeCumulative) }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: getTextForCumulative(hoveringNodeCumulative) })) })) }))] }), hoveringNode.diff !== undefined && diff !== 0 && (_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/5" }, { children: "Diff" })), _jsx("td", __assign({ className: "w-4/5" }, { children: _jsx(CopyToClipboard, __assign({ onCopy: onCopy, text: diffText }, { children: _jsx("button", __assign({ className: "cursor-pointer" }, { children: diffText })) })) }))] })), metaRows] }) })) })), _jsx("span", __assign({ className: "block text-gray-500 text-xs mt-2" }, { children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' }))] })) })) })) })) })));
85
118
  };
86
119
  var GraphTooltip = function (_a) {
87
120
  var x = _a.x, y = _a.y, unit = _a.unit, total = _a.total, hoveringNode = _a.hoveringNode, contextElement = _a.contextElement, _b = _a.isFixed, isFixed = _b === void 0 ? false : _b, _c = _a.virtualContextElement, virtualContextElement = _c === void 0 ? true : _c;
@@ -106,12 +139,15 @@ var GraphTooltip = function (_a) {
106
139
  ],
107
140
  }), styles = _e.styles, attributes = _e.attributes, popperProps = __rest(_e, ["styles", "attributes"]);
108
141
  var update = popperProps.update;
142
+ var isShiftDown = useIsShiftDown();
109
143
  useEffect(function () {
110
144
  if (contextElement != null) {
145
+ if (isShiftDown)
146
+ return;
111
147
  virtualElement.getBoundingClientRect = generateGetBoundingClientRect(contextElement, x, y);
112
148
  void (update === null || update === void 0 ? void 0 : update());
113
149
  }
114
- }, [x, y, contextElement, update]);
150
+ }, [x, y, contextElement, update, isShiftDown]);
115
151
  if (hoveringNode === undefined || hoveringNode == null)
116
152
  return _jsx(_Fragment, {});
117
153
  return isFixed ? (_jsx(GraphTooltipContent, { hoveringNode: hoveringNode, unit: unit, total: total, isFixed: isFixed })) : (_jsx("div", __assign({ ref: setPopperElement, style: styles.popper }, attributes.popper, { children: _jsx(GraphTooltipContent, { hoveringNode: hoveringNode, unit: unit, total: total, isFixed: isFixed }) })));
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Flamegraph, FlamegraphNode, FlamegraphRootNode } from '@parca/client';
2
3
  interface IcicleGraphProps {
3
4
  graph: Flamegraph;
@@ -29,6 +29,7 @@ import { scaleLinear } from 'd3-scale';
29
29
  import GraphTooltip from './GraphTooltip';
30
30
  import { getLastItem, diffColor, isSearchMatch } from '@parca/functions';
31
31
  import { useAppSelector, selectDarkMode, selectSearchNodeString } from '@parca/store';
32
+ import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
32
33
  import { hexifyAddress } from './utils';
33
34
  var RowHeight = 26;
34
35
  var icicleRectStyles = {
@@ -70,6 +71,7 @@ export function nodeLabel(node) {
70
71
  export function IcicleGraphNodes(_a) {
71
72
  var data = _a.data, x = _a.x, y = _a.y, xScale = _a.xScale, total = _a.total, totalWidth = _a.totalWidth, level = _a.level, setHoveringNode = _a.setHoveringNode, path = _a.path, setCurPath = _a.setCurPath, curPath = _a.curPath;
72
73
  var isDarkMode = useAppSelector(selectDarkMode);
74
+ var isShiftDown = useIsShiftDown();
73
75
  var nodes = curPath.length === 0 ? data : data.filter(function (d) { return d != null && curPath[0] === nodeLabel(d); });
74
76
  var nextLevel = level + 1;
75
77
  return (_jsx("g", __assign({ transform: "translate(".concat(x, ", ").concat(y, ")") }, { children: nodes.map(function (d, i) {
@@ -94,8 +96,16 @@ export function IcicleGraphNodes(_a) {
94
96
  var newXScale = nextCurPath.length === 0 && curPath.length === 1
95
97
  ? scaleLinear().domain([0, cumulative]).range([0, totalWidth])
96
98
  : xScale;
97
- var onMouseEnter = function () { return setHoveringNode(d); };
98
- var onMouseLeave = function () { return setHoveringNode(undefined); };
99
+ var onMouseEnter = function () {
100
+ if (isShiftDown)
101
+ return;
102
+ setHoveringNode(d);
103
+ };
104
+ var onMouseLeave = function () {
105
+ if (isShiftDown)
106
+ return;
107
+ setHoveringNode(undefined);
108
+ };
99
109
  return (_jsxs(React.Fragment, { children: [_jsx(IcicleRect, { x: xStart, y: 0, width: width, height: RowHeight, name: name, color: color, onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, curPath: curPath }, "rect-".concat(key)), data !== undefined && data.length > 0 && (_jsx(IcicleGraphNodes, { data: d.children, x: xStart, y: RowHeight, xScale: newXScale, total: total, totalWidth: totalWidth, level: nextLevel, setHoveringNode: setHoveringNode, path: nextPath, curPath: nextCurPath, setCurPath: setCurPath }, "node-".concat(key)))] }, "node-".concat(key)));
100
110
  }) })));
101
111
  }
@@ -103,12 +113,21 @@ var MemoizedIcicleGraphNodes = React.memo(IcicleGraphNodes);
103
113
  export function IcicleGraphRootNode(_a) {
104
114
  var node = _a.node, xScale = _a.xScale, total = _a.total, totalWidth = _a.totalWidth, setHoveringNode = _a.setHoveringNode, setCurPath = _a.setCurPath, curPath = _a.curPath;
105
115
  var isDarkMode = useAppSelector(selectDarkMode);
116
+ var isShiftDown = useIsShiftDown();
106
117
  var cumulative = parseFloat(node.cumulative);
107
118
  var diff = parseFloat(node.diff);
108
119
  var color = diffColor(diff, cumulative, isDarkMode);
109
120
  var onClick = function () { return setCurPath([]); };
110
- var onMouseEnter = function () { return setHoveringNode(node); };
111
- var onMouseLeave = function () { return setHoveringNode(undefined); };
121
+ var onMouseEnter = function () {
122
+ if (isShiftDown)
123
+ return;
124
+ setHoveringNode(node);
125
+ };
126
+ var onMouseLeave = function () {
127
+ if (isShiftDown)
128
+ return;
129
+ setHoveringNode(undefined);
130
+ };
112
131
  var path = [];
113
132
  return (_jsxs("g", __assign({ transform: 'translate(0, 0)' }, { children: [_jsx(IcicleRect, { x: 0, y: 0, width: totalWidth, height: RowHeight, name: 'root', color: color, onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, curPath: curPath }), _jsx(MemoizedIcicleGraphNodes, { data: node.children, x: 0, y: RowHeight, xScale: xScale, total: total, totalWidth: totalWidth, level: 0, setHoveringNode: setHoveringNode, path: path, curPath: curPath, setCurPath: setCurPath })] })));
114
133
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Query } from '@parca/parser';
2
3
  import { LabelsResponse, QueryServiceClient, ValuesResponse } from '@parca/client';
3
4
  interface MatchersInputProps {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  interface MetricsCircleProps {
2
3
  cx: number;
3
4
  cy: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { SingleProfileSelection } from '..';
2
3
  import { MetricsSeries as MetricsSeriesPb, Label } from '@parca/client';
3
4
  import { DateTimeRange } from '@parca/components';
@@ -35,8 +35,6 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
35
35
  // limitations under the License.
36
36
  import { useEffect, useRef, useState } from 'react';
37
37
  import * as d3 from 'd3';
38
- import MetricsSeries from '../MetricsSeries';
39
- import MetricsCircle from '../MetricsCircle';
40
38
  import { pointer } from 'd3-selection';
41
39
  import { formatForTimespan } from '@parca/functions/time';
42
40
  import { timeFormat } from '..';
@@ -46,6 +44,9 @@ import { usePopper } from 'react-popper';
46
44
  import { valueFormatter, formatDate } from '@parca/functions';
47
45
  import { DateTimeRange } from '@parca/components';
48
46
  import { useContainerDimensions } from '@parca/dynamicsize';
47
+ import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
48
+ import MetricsSeries from '../MetricsSeries';
49
+ import MetricsCircle from '../MetricsCircle';
49
50
  var MetricsGraph = function (_a) {
50
51
  var data = _a.data, from = _a.from, to = _a.to, profile = _a.profile, onSampleClick = _a.onSampleClick, onLabelClick = _a.onLabelClick, setTimeRange = _a.setTimeRange, sampleUnit = _a.sampleUnit;
51
52
  var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
@@ -133,30 +134,8 @@ export var RawMetricsGraph = function (_a) {
133
134
  var _c = useState(false), hovering = _c[0], setHovering = _c[1];
134
135
  var _d = useState(-1), relPos = _d[0], setRelPos = _d[1];
135
136
  var _e = useState([0, 0]), pos = _e[0], setPos = _e[1];
136
- var _f = useState(false), freezeTooltip = _f[0], setFreezeTooltip = _f[1];
137
137
  var metricPointRef = useRef(null);
138
- useEffect(function () {
139
- var handleShiftDown = function (event) {
140
- if (event.keyCode === 16) {
141
- setFreezeTooltip(true);
142
- }
143
- };
144
- window.addEventListener('keydown', handleShiftDown);
145
- return function () {
146
- window.removeEventListener('keydown', handleShiftDown);
147
- };
148
- }, []);
149
- useEffect(function () {
150
- var handleShiftUp = function (event) {
151
- if (event.keyCode === 16) {
152
- setFreezeTooltip(false);
153
- }
154
- };
155
- window.addEventListener('keyup', handleShiftUp);
156
- return function () {
157
- window.removeEventListener('keyup', handleShiftUp);
158
- };
159
- }, []);
138
+ var isShiftDown = useIsShiftDown();
160
139
  var time = parseFloat(profile === null || profile === void 0 ? void 0 : profile.HistoryParams().time);
161
140
  if (width === undefined || width == null) {
162
141
  width = 0;
@@ -283,7 +262,7 @@ export var RawMetricsGraph = function (_a) {
283
262
  var xCoordinateWithoutMargin = xCoordinate - margin;
284
263
  var yCoordinate = rel[1];
285
264
  var yCoordinateWithoutMargin = yCoordinate - margin;
286
- if (!freezeTooltip) {
265
+ if (!isShiftDown) {
287
266
  throttledSetPos([xCoordinateWithoutMargin, yCoordinateWithoutMargin]);
288
267
  }
289
268
  };
@@ -336,7 +315,6 @@ export var RawMetricsGraph = function (_a) {
336
315
  var selected = findSelectedProfile();
337
316
  return (_jsxs(_Fragment, { children: [highlighted != null && hovering && !dragging && pos[0] !== 0 && pos[1] !== 0 && (_jsx("div", __assign({ onMouseMove: onMouseMove, onMouseEnter: function () { return setHovering(true); }, onMouseLeave: function () { return setHovering(false); } }, { children: _jsx(MetricsTooltip, { x: pos[0] + margin, y: pos[1] + margin, highlighted: highlighted, onLabelClick: onLabelClick, contextElement: graph.current, sampleUnit: sampleUnit }) }))), _jsx("div", __assign({ ref: graph, onMouseEnter: function () {
338
317
  setHovering(true);
339
- setFreezeTooltip(false);
340
318
  }, onMouseLeave: function () { return setHovering(false); } }, { children: _jsxs("svg", __assign({ width: "".concat(width, "px"), height: "".concat(height + margin, "px"), onMouseDown: onMouseDown, onMouseUp: onMouseUp, onMouseMove: onMouseMove }, { children: [_jsx("g", __assign({ transform: "translate(".concat(margin, ", 0)") }, { children: dragging && (_jsx("g", __assign({ className: "zoom-time-rect" }, { children: _jsx("rect", { className: "bar", x: pos[0] - relPos < 0 ? pos[0] : relPos, y: 0, height: height, width: Math.abs(pos[0] - relPos), fill: 'rgba(0, 0, 0, 0.125)' }) }))) })), _jsxs("g", __assign({ transform: "translate(".concat(margin, ", ").concat(margin, ")") }, { children: [_jsx("g", __assign({ className: "lines fill-transparent" }, { children: series.map(function (s, i) { return (_jsx("g", __assign({ className: "line" }, { children: _jsx(MetricsSeries, { data: s, line: l, color: color(i.toString()), strokeWidth: hovering && highlighted != null && i === highlighted.seriesIndex
341
319
  ? lineStrokeHover
342
320
  : lineStroke, xScale: xScale, yScale: yScale }) }), i)); }) })), hovering && highlighted != null && (_jsx("g", __assign({ className: "circle-group", ref: metricPointRef, style: { fill: color(highlighted.seriesIndex.toString()) } }, { children: _jsx(MetricsCircle, { cx: highlighted.x, cy: highlighted.y }) }))), selected != null && (_jsx("g", __assign({ className: "circle-group", style: (selected === null || selected === void 0 ? void 0 : selected.seriesIndex) != null
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import * as d3 from 'd3';
2
3
  interface MetricsSeriesProps {
3
4
  data: any;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileSelection } from '..';
2
3
  import { QueryServiceClient } from '@parca/client';
3
4
  import { NavigateFunction } from '.';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient } from '@parca/client';
2
3
  import { ProfileSelection } from '..';
3
4
  import { NavigateFunction } from '../ProfileExplorer';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient } from '@parca/client';
2
3
  export declare type NavigateFunction = (path: string, queryParams: any) => void;
3
4
  interface ProfileExplorerProps {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { Flamegraph } from '@parca/client';
2
3
  interface ProfileIcicleGraphProps {
3
4
  width?: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileSelection } from '..';
2
3
  import { QueryServiceClient, QueryRangeResponse } from '@parca/client';
3
4
  import { RpcError } from '@protobuf-ts/runtime-rpc';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  declare const CompareButton: ({ disabled, onClick, }: {
2
3
  disabled: boolean;
3
4
  onClick: () => void;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  declare const MergeButton: ({ disabled, onClick, }: {
2
3
  disabled: boolean;
3
4
  onClick: () => void;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient, ProfileTypesResponse } from '@parca/client';
2
3
  import { RpcError } from '@protobuf-ts/runtime-rpc';
3
4
  import { ProfileSelection } from '..';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileType } from '@parca/parser';
2
3
  import { Label, QueryRequest, ProfileDiffSelection } from '@parca/client';
3
4
  export interface ProfileSource {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { ProfileTypesResponse } from '@parca/client';
2
3
  import { RpcError } from '@protobuf-ts/runtime-rpc';
3
4
  interface WellKnownProfile {
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient, Flamegraph, Top, Callgraph } from '@parca/client';
2
3
  import { ProfileSource } from './ProfileSource';
3
4
  import './ProfileView.styles.css';
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryServiceClient } from '@parca/client';
2
3
  import { ProfileSource } from './ProfileSource';
3
4
  declare type NavigateFunction = (path: string, queryParams: any) => void;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { TopNodeMeta, Top } from '@parca/client';
2
3
  import './TopTable.styles.css';
3
4
  interface TopTableProps {
@@ -5,5 +6,5 @@ interface TopTableProps {
5
6
  sampleUnit: string;
6
7
  }
7
8
  export declare const RowLabel: (meta: TopNodeMeta | undefined) => string;
8
- export declare const TopTable: ({ data, sampleUnit }: TopTableProps) => JSX.Element;
9
+ export declare const TopTable: ({ data: top, sampleUnit }: TopTableProps) => JSX.Element;
9
10
  export default TopTable;
@@ -1,2 +1,3 @@
1
+ /// <reference types="react" />
1
2
  declare const DiffLegend: () => JSX.Element;
2
3
  export default DiffLegend;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  interface Props {
2
3
  value: string;
3
4
  className?: string;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { QueryRequest, QueryServiceClient } from '@parca/client';
2
3
  interface Props {
3
4
  queryRequest: QueryRequest;
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-3{margin-right:.75rem}.ml-auto{margin-left:auto}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mt-1{margin-top:.25rem}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.h-36{height:9rem}.h-1{height:.25rem}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.w-full{width:100%}.w-2\/5{width:40%}.w-auto{width:auto}.w-1\/2{width:50%}.w-\[150px\]{width:150px}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-fit{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-40{width:10rem}.w-8{width:2rem}.w-16{width:4rem}.w-\[420px\]{width:420px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded{border-radius:.25rem}.rounded-none{border-radius:0}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-b{border-bottom-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-\[\#161616\]{fill:#161616}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.p-10{padding:2.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-20{padding-bottom:5rem;padding-top:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-90{opacity:.9}.opacity-100{opacity:1}.opacity-0{opacity:0}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}.dark .dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}.dark .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.dark .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.dark .dark\:fill-\[\#ffffff\]{fill:#fff}.dark .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}.dark .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}.dark .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+ /*! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-3{margin-right:.75rem}.ml-auto{margin-left:auto}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.h-36{height:9rem}.h-1{height:.25rem}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.w-full{width:100%}.w-2\/5{width:40%}.w-auto{width:auto}.w-1\/2{width:50%}.w-\[150px\]{width:150px}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-fit{width:-moz-fit-content;width:fit-content}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-40{width:10rem}.w-8{width:2rem}.w-16{width:4rem}.w-\[420px\]{width:420px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded{border-radius:.25rem}.rounded-none{border-radius:0}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-b{border-bottom-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-\[\#161616\]{fill:#161616}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.p-10{padding:2.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-20{padding-bottom:5rem;padding-top:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-90{opacity:.9}.opacity-100{opacity:1}.opacity-0{opacity:0}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}.dark .dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}.dark .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.dark .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.dark .dark\:fill-\[\#ffffff\]{fill:#fff}.dark .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}.dark .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}.dark .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.22",
3
+ "version": "0.16.49",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@iconify/react": "^3.2.2",
7
- "@parca/client": "^0.16.22",
8
- "@parca/components": "^0.16.22",
9
- "@parca/dynamicsize": "^0.16.22",
10
- "@parca/functions": "^0.16.22",
11
- "@parca/parser": "^0.16.22",
12
- "@parca/store": "^0.16.22",
7
+ "@parca/client": "^0.16.49",
8
+ "@parca/components": "^0.16.49",
9
+ "@parca/dynamicsize": "^0.16.49",
10
+ "@parca/functions": "^0.16.49",
11
+ "@parca/parser": "^0.16.49",
12
+ "@parca/store": "^0.16.49",
13
13
  "d3-scale": "^4.0.2",
14
14
  "d3-selection": "3.0.0",
15
15
  "react-copy-to-clipboard": "^5.1.0"
@@ -21,7 +21,8 @@
21
21
  "scripts": {
22
22
  "test": "jest --coverage --config ../../../jest.config.js ./src/*",
23
23
  "prepublish": "yarn build",
24
- "build": "tsc && tailwindcss -o dist/styles.css --minify && cp src/*.css ./dist/"
24
+ "build": "tsc && tailwindcss -o dist/styles.css --minify && cp src/*.css ./dist/",
25
+ "watch": "tsc-watch --onSuccess 'tailwindcss -o dist/styles.css --minify'"
25
26
  },
26
27
  "keywords": [],
27
28
  "author": "",
@@ -30,5 +31,5 @@
30
31
  "access": "public",
31
32
  "registry": "https://registry.npmjs.org/"
32
33
  },
33
- "gitHead": "b7dc2d8947b27f8fd5e62e1473c1cb41272c7850"
34
+ "gitHead": "2d1d93e9e75a15dfe18875acc6924f8f2e16206f"
34
35
  }
@@ -11,11 +11,14 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ import {CopyToClipboard} from 'react-copy-to-clipboard';
15
+ import {useState, useEffect} from 'react';
16
+ import {usePopper} from 'react-popper';
17
+
14
18
  import {CallgraphNode, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
15
19
  import {getLastItem, valueFormatter} from '@parca/functions';
20
+ import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
16
21
  import {hexifyAddress} from '../';
17
- import {useState, useEffect} from 'react';
18
- import {usePopper} from 'react-popper';
19
22
 
20
23
  interface GraphTooltipProps {
21
24
  x: number;
@@ -55,9 +58,31 @@ function generateGetBoundingClientRect(contextElement: Element, x = 0, y = 0): (
55
58
  } as DOMRect);
56
59
  }
57
60
 
58
- const TooltipMetaInfo = ({hoveringNode}: {hoveringNode: FlamegraphNode}): JSX.Element => {
61
+ const TooltipMetaInfo = ({
62
+ hoveringNode,
63
+ onCopy,
64
+ }: {
65
+ hoveringNode: FlamegraphNode;
66
+ onCopy: () => void;
67
+ }): JSX.Element => {
59
68
  if (hoveringNode.meta === undefined) return <></>;
60
69
 
70
+ const getTextForFile = (hoveringNode: FlamegraphNode): string => {
71
+ if (hoveringNode.meta === undefined) return '<unknown>';
72
+
73
+ // @ts-expect-error
74
+ return `${hoveringNode.meta.function.filename} ${
75
+ hoveringNode.meta.line?.line !== undefined && hoveringNode.meta.line?.line !== '0'
76
+ ? ` +${hoveringNode.meta.line.line.toString()}`
77
+ : `${
78
+ hoveringNode.meta.function?.startLine !== undefined &&
79
+ hoveringNode.meta.function?.startLine !== '0'
80
+ ? ` +${hoveringNode.meta.function.startLine}`
81
+ : ''
82
+ }`
83
+ }`;
84
+ };
85
+
61
86
  return (
62
87
  <>
63
88
  {hoveringNode.meta.function?.filename !== undefined &&
@@ -65,15 +90,9 @@ const TooltipMetaInfo = ({hoveringNode}: {hoveringNode: FlamegraphNode}): JSX.El
65
90
  <tr>
66
91
  <td className="w-1/5">File</td>
67
92
  <td className="w-4/5 break-all">
68
- {hoveringNode.meta.function.filename}
69
- {hoveringNode.meta.line?.line !== undefined && hoveringNode.meta.line?.line !== '0'
70
- ? ` +${hoveringNode.meta.line.line.toString()}`
71
- : `${
72
- hoveringNode.meta.function?.startLine !== undefined &&
73
- hoveringNode.meta.function?.startLine !== '0'
74
- ? ` +${hoveringNode.meta.function.startLine}`
75
- : ''
76
- }`}
93
+ <CopyToClipboard onCopy={onCopy} text={getTextForFile(hoveringNode)}>
94
+ <button className="cursor-pointer text-left">{getTextForFile(hoveringNode)}</button>
95
+ </CopyToClipboard>
77
96
  </td>
78
97
  </tr>
79
98
  )}
@@ -82,14 +101,39 @@ const TooltipMetaInfo = ({hoveringNode}: {hoveringNode: FlamegraphNode}): JSX.El
82
101
  <tr>
83
102
  <td className="w-1/5">Address</td>
84
103
  <td className="w-4/5 break-all">
85
- {' 0x' + hoveringNode.meta.location.address.toString()}
104
+ <CopyToClipboard
105
+ onCopy={onCopy}
106
+ text={' 0x' + hoveringNode.meta.location.address.toString()}
107
+ >
108
+ <button className="cursor-pointer">
109
+ {' 0x' + hoveringNode.meta.location.address.toString()}
110
+ </button>
111
+ </CopyToClipboard>
86
112
  </td>
87
113
  </tr>
88
114
  )}
89
115
  {hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.file !== '' && (
90
116
  <tr>
91
117
  <td className="w-1/5">Binary</td>
92
- <td className="w-4/5 break-all">{getLastItem(hoveringNode.meta.mapping.file)}</td>
118
+ <td className="w-4/5 break-all">
119
+ <CopyToClipboard onCopy={onCopy} text={hoveringNode.meta.mapping.file}>
120
+ <button className="cursor-pointer">
121
+ {getLastItem(hoveringNode.meta.mapping.file)}
122
+ </button>
123
+ </CopyToClipboard>
124
+ </td>
125
+ </tr>
126
+ )}
127
+ {hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.buildId !== '' && (
128
+ <tr>
129
+ <td className="w-1/5">Build Id</td>
130
+ <td className="w-4/5 break-all">
131
+ <CopyToClipboard onCopy={onCopy} text={hoveringNode.meta.mapping.buildId}>
132
+ <button className="cursor-pointer">
133
+ {getLastItem(hoveringNode.meta.mapping.buildId)}
134
+ </button>
135
+ </CopyToClipboard>
136
+ </td>
93
137
  </tr>
94
138
  )}
95
139
  </>
@@ -101,6 +145,8 @@ export interface HoveringNode extends CallgraphNode, FlamegraphRootNode {
101
145
  meta?: {[key: string]: any};
102
146
  }
103
147
 
148
+ let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
149
+
104
150
  const GraphTooltipContent = ({
105
151
  hoveringNode,
106
152
  unit,
@@ -112,6 +158,17 @@ const GraphTooltipContent = ({
112
158
  total: number;
113
159
  isFixed: boolean;
114
160
  }): JSX.Element => {
161
+ const [isCopied, setIsCopied] = useState<boolean>(false);
162
+
163
+ const onCopy = (): void => {
164
+ setIsCopied(true);
165
+
166
+ if (timeoutHandle !== null) {
167
+ clearTimeout(timeoutHandle);
168
+ }
169
+ timeoutHandle = setTimeout(() => setIsCopied(false), 3000);
170
+ };
171
+
115
172
  const hoveringNodeCumulative = parseFloat(hoveringNode.cumulative);
116
173
  const diff = hoveringNode.diff === undefined ? 0 : parseFloat(hoveringNode.diff);
117
174
  const prevValue = hoveringNodeCumulative - diff;
@@ -121,7 +178,16 @@ const GraphTooltipContent = ({
121
178
  const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
122
179
  const diffText = `${diffValueText} (${diffPercentageText})`;
123
180
  const metaRows =
124
- hoveringNode.meta === undefined ? <></> : <TooltipMetaInfo hoveringNode={hoveringNode} />;
181
+ hoveringNode.meta === undefined ? (
182
+ <></>
183
+ ) : (
184
+ <TooltipMetaInfo onCopy={onCopy} hoveringNode={hoveringNode} />
185
+ );
186
+
187
+ const getTextForCumulative = (hoveringNodeCumulative: number): string => {
188
+ return `${valueFormatter(hoveringNodeCumulative, unit, 2)} (
189
+ ${((hoveringNodeCumulative * 100) / total).toFixed(2)}%)`;
190
+ };
125
191
 
126
192
  return (
127
193
  <div className={`flex ${isFixed ? 'w-full h-36' : ''}`}>
@@ -139,12 +205,23 @@ const GraphTooltipContent = ({
139
205
  <>
140
206
  {hoveringNode.meta.function !== undefined &&
141
207
  hoveringNode.meta.function.name !== '' ? (
142
- <p>{hoveringNode.meta.function.name}</p>
208
+ <CopyToClipboard onCopy={onCopy} text={hoveringNode.meta.function.name}>
209
+ <button className="cursor-pointer text-left">
210
+ {hoveringNode.meta.function.name}
211
+ </button>
212
+ </CopyToClipboard>
143
213
  ) : (
144
214
  <>
145
215
  {hoveringNode.meta.location !== undefined &&
146
216
  parseInt(hoveringNode.meta.location.address, 10) !== 0 ? (
147
- <p>{hexifyAddress(hoveringNode.meta.location.address)}</p>
217
+ <CopyToClipboard
218
+ onCopy={onCopy}
219
+ text={hexifyAddress(hoveringNode.meta.location.address)}
220
+ >
221
+ <button className="cursor-pointer text-left">
222
+ {hexifyAddress(hoveringNode.meta.location.address)}
223
+ </button>
224
+ </CopyToClipboard>
148
225
  ) : (
149
226
  <p>unknown</p>
150
227
  )}
@@ -158,21 +235,36 @@ const GraphTooltipContent = ({
158
235
  <tbody>
159
236
  <tr>
160
237
  <td className="w-1/5">Cumulative</td>
238
+
161
239
  <td className="w-4/5">
162
- {valueFormatter(hoveringNodeCumulative, unit, 2)} (
163
- {((hoveringNodeCumulative * 100) / total).toFixed(2)}%)
240
+ <CopyToClipboard
241
+ onCopy={onCopy}
242
+ text={getTextForCumulative(hoveringNodeCumulative)}
243
+ >
244
+ <button className="cursor-pointer">
245
+ {getTextForCumulative(hoveringNodeCumulative)}
246
+ </button>
247
+ </CopyToClipboard>
164
248
  </td>
165
249
  </tr>
166
250
  {hoveringNode.diff !== undefined && diff !== 0 && (
167
251
  <tr>
168
252
  <td className="w-1/5">Diff</td>
169
- <td className="w-4/5">{diffText}</td>
253
+ <td className="w-4/5">
254
+ <CopyToClipboard onCopy={onCopy} text={diffText}>
255
+ <button className="cursor-pointer">{diffText}</button>
256
+ </CopyToClipboard>
257
+ </td>
170
258
  </tr>
171
259
  )}
172
260
  {metaRows}
173
261
  </tbody>
174
262
  </table>
175
263
  </span>
264
+
265
+ <span className="block text-gray-500 text-xs mt-2">
266
+ {isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.'}
267
+ </span>
176
268
  </div>
177
269
  </div>
178
270
  </div>
@@ -218,13 +310,16 @@ const GraphTooltip = ({
218
310
  );
219
311
 
220
312
  const update = popperProps.update;
313
+ const isShiftDown = useIsShiftDown();
221
314
 
222
315
  useEffect(() => {
223
316
  if (contextElement != null) {
317
+ if (isShiftDown) return;
318
+
224
319
  virtualElement.getBoundingClientRect = generateGetBoundingClientRect(contextElement, x, y);
225
320
  void update?.();
226
321
  }
227
- }, [x, y, contextElement, update]);
322
+ }, [x, y, contextElement, update, isShiftDown]);
228
323
 
229
324
  if (hoveringNode === undefined || hoveringNode == null) return <></>;
230
325
 
@@ -16,11 +16,12 @@ import React, {MouseEvent, useEffect, useRef, useState} from 'react';
16
16
  import {throttle} from 'lodash';
17
17
  import {pointer} from 'd3-selection';
18
18
  import {scaleLinear} from 'd3-scale';
19
+
19
20
  import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
20
21
  import GraphTooltip from './GraphTooltip';
21
22
  import {getLastItem, diffColor, isSearchMatch} from '@parca/functions';
22
23
  import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
23
-
24
+ import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
24
25
  import {hexifyAddress} from './utils';
25
26
  import type {HoveringNode} from './GraphTooltip';
26
27
 
@@ -161,6 +162,7 @@ export function IcicleGraphNodes({
161
162
  curPath,
162
163
  }: IcicleGraphNodesProps): JSX.Element {
163
164
  const isDarkMode = useAppSelector(selectDarkMode);
165
+ const isShiftDown = useIsShiftDown();
164
166
 
165
167
  const nodes =
166
168
  curPath.length === 0 ? data : data.filter(d => d != null && curPath[0] === nodeLabel(d));
@@ -200,8 +202,16 @@ export function IcicleGraphNodes({
200
202
  ? scaleLinear().domain([0, cumulative]).range([0, totalWidth])
201
203
  : xScale;
202
204
 
203
- const onMouseEnter = (): void => setHoveringNode(d);
204
- const onMouseLeave = (): void => setHoveringNode(undefined);
205
+ const onMouseEnter = (): void => {
206
+ if (isShiftDown) return;
207
+
208
+ setHoveringNode(d);
209
+ };
210
+ const onMouseLeave = (): void => {
211
+ if (isShiftDown) return;
212
+
213
+ setHoveringNode(undefined);
214
+ };
205
215
 
206
216
  return (
207
217
  <React.Fragment key={`node-${key}`}>
@@ -253,14 +263,24 @@ export function IcicleGraphRootNode({
253
263
  curPath,
254
264
  }: IcicleGraphRootNodeProps): JSX.Element {
255
265
  const isDarkMode = useAppSelector(selectDarkMode);
266
+ const isShiftDown = useIsShiftDown();
256
267
 
257
268
  const cumulative = parseFloat(node.cumulative);
258
269
  const diff = parseFloat(node.diff);
259
270
  const color = diffColor(diff, cumulative, isDarkMode);
260
271
 
261
272
  const onClick = (): void => setCurPath([]);
262
- const onMouseEnter = (): void => setHoveringNode(node);
263
- const onMouseLeave = (): void => setHoveringNode(undefined);
273
+ const onMouseEnter = (): void => {
274
+ if (isShiftDown) return;
275
+
276
+ setHoveringNode(node);
277
+ };
278
+ const onMouseLeave = (): void => {
279
+ if (isShiftDown) return;
280
+
281
+ setHoveringNode(undefined);
282
+ };
283
+
264
284
  const path: string[] = [];
265
285
 
266
286
  return (
@@ -13,8 +13,6 @@
13
13
 
14
14
  import React, {useEffect, useRef, useState} from 'react';
15
15
  import * as d3 from 'd3';
16
- import MetricsSeries from '../MetricsSeries';
17
- import MetricsCircle from '../MetricsCircle';
18
16
  import {pointer} from 'd3-selection';
19
17
  import {formatForTimespan} from '@parca/functions/time';
20
18
  import {SingleProfileSelection, timeFormat} from '..';
@@ -26,6 +24,10 @@ import type {VirtualElement} from '@popperjs/core';
26
24
  import {valueFormatter, formatDate} from '@parca/functions';
27
25
  import {DateTimeRange} from '@parca/components';
28
26
  import {useContainerDimensions} from '@parca/dynamicsize';
27
+ import useIsShiftDown from '@parca/components/src/hooks/useIsShiftDown';
28
+
29
+ import MetricsSeries from '../MetricsSeries';
30
+ import MetricsCircle from '../MetricsCircle';
29
31
 
30
32
  interface RawMetricsGraphProps {
31
33
  data: MetricsSeriesPb[];
@@ -244,34 +246,8 @@ export const RawMetricsGraph = ({
244
246
  const [hovering, setHovering] = useState(false);
245
247
  const [relPos, setRelPos] = useState(-1);
246
248
  const [pos, setPos] = useState([0, 0]);
247
- const [freezeTooltip, setFreezeTooltip] = useState(false);
248
249
  const metricPointRef = useRef(null);
249
-
250
- useEffect(() => {
251
- const handleShiftDown = (event: {keyCode: number}): void => {
252
- if (event.keyCode === 16) {
253
- setFreezeTooltip(true);
254
- }
255
- };
256
- window.addEventListener('keydown', handleShiftDown);
257
-
258
- return () => {
259
- window.removeEventListener('keydown', handleShiftDown);
260
- };
261
- }, []);
262
-
263
- useEffect(() => {
264
- const handleShiftUp = (event: {keyCode: number}): void => {
265
- if (event.keyCode === 16) {
266
- setFreezeTooltip(false);
267
- }
268
- };
269
- window.addEventListener('keyup', handleShiftUp);
270
-
271
- return () => {
272
- window.removeEventListener('keyup', handleShiftUp);
273
- };
274
- }, []);
250
+ const isShiftDown = useIsShiftDown();
275
251
 
276
252
  const time: number = parseFloat(profile?.HistoryParams().time);
277
253
 
@@ -431,7 +407,7 @@ export const RawMetricsGraph = ({
431
407
  const yCoordinate = rel[1];
432
408
  const yCoordinateWithoutMargin = yCoordinate - margin;
433
409
 
434
- if (!freezeTooltip) {
410
+ if (!isShiftDown) {
435
411
  throttledSetPos([xCoordinateWithoutMargin, yCoordinateWithoutMargin]);
436
412
  }
437
413
  };
@@ -505,7 +481,6 @@ export const RawMetricsGraph = ({
505
481
  ref={graph}
506
482
  onMouseEnter={function () {
507
483
  setHovering(true);
508
- setFreezeTooltip(false);
509
484
  }}
510
485
  onMouseLeave={() => setHovering(false)}
511
486
  >