@samitouri / QOS-React / commits / 565d739569

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;