Added commit time filter
Brian Vaughn committed
Mar 10, 2019 at 10:13 UTC
565d739569ca6707ff7fc75f776dce1791ba1049
7 files changed
+162
-14
src/devtools/views/Profiler/FilterModal.css
new
+31
@@ -0,0 +1,31 @@
1
+.Background {
2
+ position: absolute;
3
+ width: 100%;
4
+ height: 100%;
5
+ background-color: var(--color-modal-background);
6
+}
7
+
8
+.Modal {
9
+ position: absolute;
10
+ top: 5px;
11
+ right: 5px;
12
+ display: inline-block;
13
+ background-color: var(--color-background);
14
+ padding: 1rem;
15
+ border: 1px solid var(--color-border);
16
+ border-radius: 0.25rem;
17
+}
18
+
19
+.Input {
20
+ width: 40px;
21
+ background: none;
22
+ border: 1px solid transparent;
23
+ color: var(--color-attribute-editable-value);
24
+ border-radius: 0.125rem;
25
+ font-family: var(--font-family-monospace);
26
+ font-size: var(--font-size-monospace-normal);
27
+}
28
+.Input:focus {
29
+ background-color: var(--color-button-background-focus);
30
+ outline: none;
31
+}
src/devtools/views/Profiler/FilterModal.js
new
+75
@@ -0,0 +1,75 @@
1
+// @flow
2
+
3
+import React, { useCallback, useEffect, useRef } from 'react';
4
+import { useLocalStorage, useModalDismissSignal } from '../hooks';
5
+
6
+import styles from './FilterModal.css';
7
+
8
+type Props = {|
9
+ dismissModal: Function,
10
+|};
11
+
12
+export default function FilterModal({ dismissModal }: Props) {
13
+ const [isEnabled, setIsEnabled] = useLocalStorage<boolean>(
14
+ 'minCommitDurationFilterEnabled',
15
+ false
16
+ );
17
+ const [value, setValue] = useLocalStorage<number>(
18
+ 'minCommitDurationFilter',
19
+ 0
20
+ );
21
+
22
+ const handleNumberChange = useCallback(
23
+ ({ currentTarget }) => {
24
+ const newValue = parseInt(currentTarget.value, 10);
25
+ setValue(Number.isNaN(newValue) || newValue <= 0 ? 0 : newValue);
26
+ },
27
+ [setValue]
28
+ );
29
+
30
+ const handleEnabledChange = useCallback(
31
+ ({ currentTarget }) => {
32
+ setIsEnabled(currentTarget.checked);
33
+ if (currentTarget.checked) {
34
+ if (inputRef.current !== null) {
35
+ inputRef.current.focus();
36
+ }
37
+ }
38
+ },
39
+ [setIsEnabled]
40
+ );
41
+
42
+ const inputRef = useRef<HTMLInputElement | null>(null);
43
+ const modalRef = useRef<HTMLDivElement | null>(null);
44
+
45
+ useModalDismissSignal(modalRef, dismissModal);
46
+
47
+ useEffect(() => {
48
+ if (inputRef.current !== null) {
49
+ inputRef.current.focus();
50
+ }
51
+ }, [inputRef]);
52
+
53
+ return (
54
+ <div className={styles.Background}>
55
+ <div className={styles.Modal} ref={modalRef}>
56
+ <label>
57
+ <input
58
+ checked={isEnabled}
59
+ onChange={handleEnabledChange}
60
+ type="checkbox"
61
+ />{' '}
62
+ Hide commits below
63
+ </label>{' '}
64
+ <input
65
+ className={styles.Input}
66
+ onChange={handleNumberChange}
67
+ ref={inputRef}
68
+ type="number"
69
+ value={value}
70
+ />{' '}
71
+ (ms)
72
+ </div>
73
+ </div>
74
+ );
75
+}
src/devtools/views/Profiler/Profiler.css
+1
@@ -10,6 +10,7 @@
10
}
11
12
.Content {
13
+ position: relative;
14
flex: 1 1 auto;
15
display: flex;
16
flex-direction: row;
src/devtools/views/Profiler/Profiler.js
+20
-13
@@ -1,10 +1,11 @@
1
// @flow
2
3
-import React, { useContext, useState } from 'react';
3
+import React, { useCallback, useContext, useState } from 'react';
4
import { ProfilerContext, ProfilerContextController } from './ProfilerContext';
5
import Button from '../Button';
6
import ButtonIcon from '../ButtonIcon';
7
import TabBar from '../TabBar';
8
+import FilterModal from './FilterModal';
9
import RecordToggle from './RecordToggle';
10
11
import styles from './Profiler.css';
@@ -22,6 +23,10 @@ export default function ProfilerOuter(_: Props) {
23
function ProfilerInner(_: Props) {
24
const { hasProfilingData, isProfiling } = useContext(ProfilerContext);
25
const [tab, setTab] = useState('flame-chart');
26
+ const [isFilterModalShowing, setIsFilterModalShowing] = useState(false);
27
+
28
+ const showFilterModal = useCallback(() => setIsFilterModalShowing(true));
29
+ const dismissFilterModal = useCallback(() => setIsFilterModalShowing(false));
30
31
let view = null;
32
if (isProfiling) {
@@ -52,12 +57,16 @@ function ProfilerInner(_: Props) {
57
tabs={tabs}
58
/>
59
<div className={styles.Spacer} />
55
- <Button disabled title="Filter commits by duration">
56
- {/* TODO (profiling) Wire up filter button */}
60
+ <Button onClick={showFilterModal} title="Filter commits by duration">
61
<ButtonIcon type="filter" />
62
</Button>
63
</div>
60
- <div className={styles.Content}>{view}</div>
64
+ <div className={styles.Content}>
65
+ {view}
66
+ {isFilterModalShowing && (
67
+ <FilterModal dismissModal={dismissFilterModal} />
68
+ )}
69
+ </div>
70
</div>
71
);
72
}
@@ -77,13 +86,11 @@ const NoProfilingData = () => (
86
</div>
87
);
88
80
-const RecortdingInProgress = () => {
81
- return (
82
- <div className={styles.Column}>
83
- <div className={styles.Header}>Profiling is in progress...</div>
84
- <div className={styles.Row}>
85
- Click the record button <RecordToggle /> to stop recording.
86
- </div>
89
+const RecortdingInProgress = () => (
90
+ <div className={styles.Column}>
91
+ <div className={styles.Header}>Profiling is in progress...</div>
92
+ <div className={styles.Row}>
93
+ Click the record button <RecordToggle /> to stop recording.
94
</div>
88
- );
89
-};
95
+ </div>
96
+);
src/devtools/views/Settings/SettingsContext.js
+1
@@ -141,6 +141,7 @@ function updateThemeVariables(theme: Theme): void {
141
updateStyleHelper(theme, 'color-dimmest');
142
updateStyleHelper(theme, 'color-jsx-arrow-brackets');
143
updateStyleHelper(theme, 'color-jsx-arrow-brackets-inverted');
144
+ updateStyleHelper(theme, 'color-modal-background');
145
updateStyleHelper(theme, 'color-record-active');
146
updateStyleHelper(theme, 'color-record-hover');
147
updateStyleHelper(theme, 'color-record-inactive');
src/devtools/views/hooks.js
+32
-1
@@ -1,6 +1,6 @@
1
// @flow
2
3
-import { useCallback, useLayoutEffect, useState } from 'react';
3
+import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
4
5
// Forked from https://usehooks.com/useLocalStorage/
6
export function useLocalStorage<T>(
@@ -52,3 +52,34 @@ export function useLocalStorage<T>(
52
53
return [storedValue, setValue];
54
}
55
+
56
+export function useModalDismissSignal(
57
+ modalRef: React$Ref<any>,
58
+ dismissCallback: Function
59
+): void {
60
+ useEffect(() => {
61
+ const handleKeyDown = ({ key }: any) => {
62
+ if (key === 'Escape') {
63
+ dismissCallback();
64
+ }
65
+ };
66
+
67
+ const handleMouseOrTouch = ({ target }: any) => {
68
+ // $FlowFixMe
69
+ if (modalRef.current !== null && !modalRef.current.contains(target)) {
70
+ dismissCallback();
71
+ }
72
+ };
73
+
74
+ const body = ((document.body: any): HTMLBodyElement);
75
+ body.addEventListener('keydown', handleKeyDown);
76
+ body.addEventListener('mousedown', handleMouseOrTouch);
77
+ body.addEventListener('touchstart', handleMouseOrTouch);
78
+
79
+ return () => {
80
+ body.removeEventListener('keydown', handleKeyDown);
81
+ body.removeEventListener('mousedown', handleMouseOrTouch);
82
+ body.removeEventListener('touchstart', handleMouseOrTouch);
83
+ };
84
+ }, [modalRef, dismissCallback]);
85
+}
src/devtools/views/root.css
+2
@@ -23,6 +23,7 @@
23
--light-color-dimmest: #eff0f1;
24
--light-color-jsx-arrow-brackets: #333333;
25
--light-color-jsx-arrow-brackets-inverted: rgba(255, 255, 255, 0.7);
26
+ --light-color-modal-background: rgba(255, 255, 255, 0.25);
27
--light-color-record-active: #fc3a4b;
28
--light-color-record-hover: #000000;
29
--light-color-record-inactive: #cfd1d5;
@@ -52,6 +53,7 @@
53
--dark-color-dimmest: #4f5766;
54
--dark-color-jsx-arrow-brackets: #777d88;
55
--dark-color-jsx-arrow-brackets-inverted: rgba(255, 255, 255, 0.7);
56
+ --dark-color-modal-background: rgba(0, 0, 0, 0.25);
57
--dark-color-record-active: #fc3a4b;
58
--dark-color-record-hover: #ffffff;
59
--dark-color-record-inactive: #777d88;