diff --git a/package.json b/package.json index 66225e7dbf..6c5652b236 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "react-dom": "^18.3.1", "react-intersection-observer": "^10.0.3", "react-redux": "^9.2.0", - "react-splitter-layout": "^4.0.0", "react-transition-group": "^4.4.5", "redux": "^5.0.1", "redux-logger": "^3.0.6", diff --git a/src/actions/app.ts b/src/actions/app.ts index aabb1706b5..5792312927 100644 --- a/src/actions/app.ts +++ b/src/actions/app.ts @@ -109,10 +109,6 @@ export function changeSidebarOpenState(tab: TabSlug, isOpen: boolean): Action { return { type: 'CHANGE_SIDEBAR_OPEN_STATE', tab, isOpen }; } -export function invalidatePanelLayout(): Action { - return { type: 'INCREMENT_PANEL_LAYOUT_GENERATION' as const }; -} - /** * The viewport component provides a hint to use shift to zoom scroll. The first * time a user does this, the hint goes away. diff --git a/src/components/app/BottomBox.css b/src/components/app/BottomBox.css index dc80d0345b..29e7afb40c 100644 --- a/src/components/app/BottomBox.css +++ b/src/components/app/BottomBox.css @@ -2,13 +2,21 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +.bottom-box { + display: flex; + min-height: 0; + flex-flow: row nowrap; +} + .bottom-box-pane { --internal-sourceview-background-color: var(--grey-20); --internal-close-icon: url(../../../res/img/svg/close-dark.svg); --internal-assembly-icon: url(../../../res/img/svg/asm-icon.svg); display: flex; - height: 100%; /* direct child of SplitterLayout */ + overflow: hidden; + min-width: 0; + flex: 1; flex-flow: column nowrap; color: var(--base-foreground-color); } @@ -23,7 +31,7 @@ background: var(--internal-sourceview-background-color); } -.bottom-box .layout-splitter { +.bottom-box .resizableWithSplitterSplitter { position: relative; /* containing block for absolute ::before */ width: 1px; border: none; @@ -31,7 +39,7 @@ } /* Provide 3px extra grabbable surface on each side of the splitter */ -.bottom-box .layout-splitter::before { +.bottom-box .resizableWithSplitterSplitter::before { position: absolute; z-index: var(--z-bottom-box); display: block; @@ -44,6 +52,7 @@ overflow: hidden; height: 24px; flex-flow: row; + flex-shrink: 0; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--panel-border-color); diff --git a/src/components/app/BottomBox.tsx b/src/components/app/BottomBox.tsx index 96558f3b9c..07ffdb055d 100644 --- a/src/components/app/BottomBox.tsx +++ b/src/components/app/BottomBox.tsx @@ -3,11 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import React from 'react'; -import SplitterLayout from 'react-splitter-layout'; import classNames from 'classnames'; import { SourceView } from '../shared/SourceView'; import { AssemblyView } from '../shared/AssemblyView'; +import { ResizableWithSplitter } from '../shared/ResizableWithSplitter'; import { AssemblyViewToggleButton } from './AssemblyViewToggleButton'; import { AssemblyViewNativeSymbolNavigator } from './AssemblyViewNativeSymbolNavigator'; import { IonGraphView } from '../shared/IonGraphView'; @@ -217,43 +217,48 @@ class BottomBoxImpl extends React.PureComponent { return (
- -
-
-

{path ?? '(no source file)'}

- {assemblyViewIsOpen ? null : trailingHeaderButtons} -
-
- {displayIonGraph ? ( - - ) : null} - {displaySourceView ? ( - - ) : null} - {sourceViewCode !== undefined && - sourceViewCode.type === 'LOADING' ? ( - - ) : null} - {sourceViewCode !== undefined && - sourceViewCode.type === 'ERROR' ? ( - - ) : null} -
+
+
+

{path ?? '(no source file)'}

+ {assemblyViewIsOpen ? null : trailingHeaderButtons} +
+
+ {displayIonGraph ? ( + + ) : null} + {displaySourceView ? ( + + ) : null} + {sourceViewCode !== undefined && + sourceViewCode.type === 'LOADING' ? ( + + ) : null} + {sourceViewCode !== undefined && sourceViewCode.type === 'ERROR' ? ( + + ) : null}
+
- {assemblyViewIsOpen ? ( + {assemblyViewIsOpen ? ( +

@@ -289,8 +294,8 @@ class BottomBoxImpl extends React.PureComponent { ) : null}

- ) : null} - +
+ ) : null}
); } diff --git a/src/components/app/Details.css b/src/components/app/Details.css index ed1cafd550..43a051246c 100644 --- a/src/components/app/Details.css +++ b/src/components/app/Details.css @@ -3,8 +3,11 @@ --internal-sidebar-expand-icon: url(../../../res/img/svg/pane-expand.svg); display: flex; + + /* If .Details is squished to a very small size between the sidebar and/or bottom bar, + don't spill out our contents over those other elements. */ overflow: clip; - flex: auto; + flex: 1; flex-direction: column; } diff --git a/src/components/app/DetailsContainer.css b/src/components/app/DetailsContainer.css index 21bcae24d1..c099f05b53 100644 --- a/src/components/app/DetailsContainer.css +++ b/src/components/app/DetailsContainer.css @@ -1,26 +1,29 @@ -.DetailsContainer .layout-pane > * { - width: 100%; - height: 100%; +.DetailsContainer { + position: relative; + z-index: 0; + display: flex; box-sizing: border-box; + flex: 1; + flex-flow: row nowrap; + contain: size; } -.DetailsContainer .layout-pane:not(.layout-pane-primary) { - max-width: 600px; +.DetailsContainer .resizableWithSplitterInner > * { + min-width: 0; + flex: 1; } -/* overriding defaults from splitter-layout */ -.DetailsContainer { - /* SplitterLayout injects position: absolute, this conflicts with our using - * Flex Layout. */ - position: unset; +.DetailsContainerResizableSidebarWrapper { + max-width: 600px; } -.DetailsContainer.splitter-layout > .layout-splitter { - border-top: 1px solid var(--panel-border-color); - border-left: 1px solid var(--panel-border-color); - background: var(--panel-background-color); +/* overriding defaults from ResizableWithSplitter.css */ +.DetailsContainer .resizableWithSplitterSplitter { + border-top: 1px solid var(--grey-30); + border-left: 1px solid var(--grey-30); + background: var(--grey-10); /* Same background as sidebars */ } -.DetailsContainer.splitter-layout > .layout-splitter:hover { - background: var(--panel-background-color); +.DetailsContainer .resizableWithSplitterSplitter:hover { + background: var(--grey-30); /* same as the border above */ } diff --git a/src/components/app/DetailsContainer.tsx b/src/components/app/DetailsContainer.tsx index 5f3911efbd..d8b137d6f3 100644 --- a/src/components/app/DetailsContainer.tsx +++ b/src/components/app/DetailsContainer.tsx @@ -1,13 +1,11 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// React imported for JSX -import SplitterLayout from 'react-splitter-layout'; import { Details } from './Details'; +import { ResizableWithSplitter } from 'firefox-profiler/components/shared/ResizableWithSplitter'; import { selectSidebar } from 'firefox-profiler/components/sidebar'; -import { invalidatePanelLayout } from 'firefox-profiler/actions/app'; import { getSelectedTab } from 'firefox-profiler/selectors/url-state'; import { getIsSidebarOpen } from 'firefox-profiler/selectors/app'; import explicitConnect from 'firefox-profiler/utils/connect'; @@ -22,39 +20,33 @@ type StateProps = { readonly isSidebarOpen: boolean; }; -type DispatchProps = { - readonly invalidatePanelLayout: typeof invalidatePanelLayout; -}; - -type Props = ConnectedProps<{}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, {}>; -function DetailsContainerImpl({ - selectedTab, - isSidebarOpen, - invalidatePanelLayout, -}: Props) { +function DetailsContainerImpl({ selectedTab, isSidebarOpen }: Props) { const Sidebar = selectSidebar(selectedTab); return ( - +
- {Sidebar && isSidebarOpen ? : null} - + {Sidebar && isSidebarOpen ? ( + + + + ) : null} +
); } -export const DetailsContainer = explicitConnect<{}, StateProps, DispatchProps>({ +export const DetailsContainer = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state) => ({ selectedTab: getSelectedTab(state), isSidebarOpen: getIsSidebarOpen(state), }), - mapDispatchToProps: { - invalidatePanelLayout, - }, component: DetailsContainerImpl, }); diff --git a/src/components/app/ProfileViewer.css b/src/components/app/ProfileViewer.css index 1fdf52b78f..447647b6a1 100644 --- a/src/components/app/ProfileViewer.css +++ b/src/components/app/ProfileViewer.css @@ -47,6 +47,10 @@ } .profileViewer { + /* Create a stacking context, so that the KeyboardShortcut can overlay the profile + viewer. */ + position: relative; + z-index: 0; display: flex; min-width: 0; /* This allows Flexible Layout to shrink this further than its min-content */ flex: 1; @@ -109,21 +113,6 @@ } } -.profileViewerSplitter { - /* Position relative for layout. Don't set z-index here to avoid creating a - stacking context that would trap the context menu below the screenshot hover. - This allows both the screenshot hover and context menu to be compared - at the .profileViewer level, ensuring the context menu appears on top. */ - position: relative; -} - -.profileViewerSplitter > .layout-pane:not(.layout-pane-primary) { - display: flex; - overflow: hidden; - max-height: var(--profile-viewer-splitter-max-height); - flex-direction: column; -} - .profileViewerTopBar { display: flex; height: 24px; diff --git a/src/components/app/ProfileViewer.tsx b/src/components/app/ProfileViewer.tsx index e00a87117d..9ffdac1733 100644 --- a/src/components/app/ProfileViewer.tsx +++ b/src/components/app/ProfileViewer.tsx @@ -5,6 +5,7 @@ import { PureComponent } from 'react'; import explicitConnect from 'firefox-profiler/utils/connect'; +import { ResizableWithSplitter } from 'firefox-profiler/components/shared/ResizableWithSplitter'; import { DetailsContainer } from './DetailsContainer'; import { SourceCodeFetcher } from './SourceCodeFetcher'; import { AssemblyCodeFetcher } from './AssemblyCodeFetcher'; @@ -20,8 +21,6 @@ import { KeyboardShortcut } from './KeyboardShortcut'; import { returnToZipFileList } from 'firefox-profiler/actions/zipped-profiles'; import { Timeline } from 'firefox-profiler/components/timeline'; import { getHasZipFile } from 'firefox-profiler/selectors/zipped-profiles'; -import SplitterLayout from 'react-splitter-layout'; -import { invalidatePanelLayout } from 'firefox-profiler/actions/app'; import { getTimelineHeight } from 'firefox-profiler/selectors/app'; import { getIsBottomBoxOpen } from 'firefox-profiler/selectors/url-state'; import { @@ -54,7 +53,6 @@ type StateProps = { type DispatchProps = { readonly returnToZipFileList: typeof returnToZipFileList; - readonly invalidatePanelLayout: typeof invalidatePanelLayout; }; type Props = ConnectedProps<{}, StateProps, DispatchProps>; @@ -64,7 +62,6 @@ class ProfileViewerImpl extends PureComponent { const { hasZipFile, returnToZipFileList, - invalidatePanelLayout, timelineHeight, isUploading, uploadProgress, @@ -128,30 +125,26 @@ class ProfileViewerImpl extends PureComponent { /> ) : null}
- - + + {isBottomBoxOpen ? ( + - - {isBottomBoxOpen ? : null} - - + + + ) : null}
@@ -178,7 +171,6 @@ export const ProfileViewer = explicitConnect<{}, StateProps, DispatchProps>({ }), mapDispatchToProps: { returnToZipFileList, - invalidatePanelLayout, }, component: ProfileViewerImpl, }); diff --git a/src/components/shared/Draggable.tsx b/src/components/shared/Draggable.tsx index c463d035e6..c0d6220f3d 100644 --- a/src/components/shared/Draggable.tsx +++ b/src/components/shared/Draggable.tsx @@ -12,15 +12,23 @@ export type OnMove = ( ) => void; type Props = { - value: T; + value?: T; + getInitialValue?: () => T; onMove: OnMove; className: string; children?: React.ReactNode; }; -type State = { - dragging: boolean; -}; +type State = + | { + dragging: false; + } + | { + dragging: true; + startValue: T; + startX: number; + startY: number; + }; /** * A component that reports mouse dragging (left mouse button only) in its @@ -30,13 +38,9 @@ type State = { * x and y deltas compared to the mouse position at mousedown. * During the drag, the additional className 'dragging' is set on the element. */ -export class Draggable extends React.PureComponent, State> { +export class Draggable extends React.PureComponent, State> { _container: HTMLDivElement | null = null; - _handlers: { - mouseMoveHandler: (param: MouseEvent) => void; - mouseUpHandler: (param: MouseEvent) => void; - } | null = null; - override state = { + override state: State = { dragging: false, }; @@ -44,79 +48,61 @@ export class Draggable extends React.PureComponent, State> { this._container = c; }; - _onMouseDown = (e: React.MouseEvent) => { + _getInitialValue = (): T => { + if (this.props.value !== undefined) { + return this.props.value; + } + if (this.props.getInitialValue !== undefined) { + return this.props.getInitialValue(); + } + throw new Error('Missing value in Draggable'); + }; + + _onPointerDown = (e: React.PointerEvent) => { if (!this._container || e.button !== 0) { return; } e.stopPropagation(); e.preventDefault(); - this.setState({ dragging: true }); - - const mouseDownX = e.pageX; - const mouseDownY = e.pageY; - const startValue = this.props.value; - - const mouseMoveHandler = (e: MouseEvent) => { - this.props.onMove( - startValue, - e.pageX - mouseDownX, - e.pageY - mouseDownY, - true - ); - // Note: no stopPropagation, so that other handlers (eg: screenshot - // hovers) can also get the event and handle it. - e.preventDefault(); - }; - const mouseUpHandler = (e: MouseEvent) => { - this.props.onMove( - startValue, - e.pageX - mouseDownX, - e.pageY - mouseDownY, - false - ); - e.stopPropagation(); - e.preventDefault(); - this._uninstallMoveAndUpHandlers(); - this.setState({ dragging: false }); - }; - - this._installMoveAndUpHandlers(mouseMoveHandler, mouseUpHandler); + this._container?.setPointerCapture(e.pointerId); + this.setState({ + dragging: true, + startValue: this._getInitialValue(), + startX: e.pageX, + startY: e.pageY, + }); }; - _installMoveAndUpHandlers( - mouseMoveHandler: (param: MouseEvent) => void, - mouseUpHandler: (param: MouseEvent) => void - ) { - // Unregister any leftover old handlers, in case we didn't get a mouseup for the previous - // drag (e.g. when tab switching during a drag, or when ctrl+clicking on macOS). - this._uninstallMoveAndUpHandlers(); + _onPointerMove = (e: React.PointerEvent) => { + if (!this.state.dragging) { + return; + } - this._handlers = { mouseMoveHandler, mouseUpHandler }; - window.addEventListener('mousemove', mouseMoveHandler, true); - window.addEventListener('mouseup', mouseUpHandler, true); - } + const { startValue, startX, startY } = this.state; + this.props.onMove(startValue, e.pageX - startX, e.pageY - startY, true); + }; - _uninstallMoveAndUpHandlers() { - if (this._handlers) { - const { mouseMoveHandler, mouseUpHandler } = this._handlers; - window.removeEventListener('mousemove', mouseMoveHandler, true); - window.removeEventListener('mouseup', mouseUpHandler, true); - this._handlers = null; + _onPointerUp = (e: React.PointerEvent) => { + if (!this.state.dragging) { + return; } - } - override componentWillUnmount() { - this._uninstallMoveAndUpHandlers(); - } + const { startValue, startX, startY } = this.state; + this.props.onMove(startValue, e.pageX - startX, e.pageY - startY, false); + this.setState({ dragging: false }); + }; override render() { const { children, className } = this.props; + const { dragging } = this.state; return (
{children} diff --git a/src/components/shared/ResizableWithSplitter.css b/src/components/shared/ResizableWithSplitter.css new file mode 100644 index 0000000000..b1f8bcf63b --- /dev/null +++ b/src/components/shared/ResizableWithSplitter.css @@ -0,0 +1,34 @@ +.resizableWithSplitterOuter, +.resizableWithSplitterInner { + display: flex; + min-width: 0; + min-height: 0; + flex-direction: inherit; +} + +.resizableWithSplitterInner { + flex: 1; +} + +.resizableWithSplitterSplitter { + flex-shrink: 0; + background: #ccc; +} + +.resizableWithSplitterSplitter:hover { + background: #bbb; +} + +.resizableWithSplitterSplitter.dragging { + background: #aaa; +} + +.resizableWithSplitterSplitter.resizesWidth { + width: 5px; + cursor: col-resize; +} + +.resizableWithSplitterSplitter.resizesHeight { + height: 5px; + cursor: row-resize; +} diff --git a/src/components/shared/ResizableWithSplitter.tsx b/src/components/shared/ResizableWithSplitter.tsx new file mode 100644 index 0000000000..8a9cbdf851 --- /dev/null +++ b/src/components/shared/ResizableWithSplitter.tsx @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// @flow + +import * as React from 'react'; + +import { Draggable } from './Draggable'; +import classNames from 'classnames'; + +import type { CssPixels } from '../../types/units'; + +import './ResizableWithSplitter.css'; + +type Props = { + // If 'start', the splitter is placed before the resized element, otherwise after. + splitterPosition: 'start' | 'end'; + // The CSS property which gets changed if the splitter is dragged. + controlledProperty: + | 'width' + | 'height' + | 'min-width' + | 'min-height' + | 'max-width' + | 'max-height'; + // The initial size, as a valid CSS length. For example "200px" or "30%". + // This prop is read only once, during componentDidMount. + initialSize: string; + // True if the value for controlledProperty should be set as a percentage, + // false if it should be set in CSS "px". + percent: boolean; + // An extra className for the outer div. + className?: string; + // The sized contents. These are placed inside an element with the className + // "resizableWithSplitterInner" and should have a non-zero CSS "flex" value + // set on them, so that they resize when "resizableWithSplitterInner" resizes. + children: React.ReactNode; +}; + +type State = { + size: string; +}; + +type Size = { width: CssPixels; height: CssPixels }; + +export class ResizableWithSplitter extends React.PureComponent { + override state = { + size: '0px', + }; + + _outer = React.createRef(); + + _onMove = ( + originalValue: { outerSize: Size; parentSize: Size }, + dx: number, + dy: number + ) => { + const { controlledProperty, splitterPosition, percent } = this.props; + if (controlledProperty.endsWith('width')) { + const growsToRight = splitterPosition === 'end'; + const originalWidth = originalValue.outerSize.width; + const adjustedWidth = growsToRight + ? Math.max(0, originalWidth + dx) + : Math.max(0, originalWidth - dx); + if (percent) { + const fraction = adjustedWidth / originalValue.parentSize.width; + this.setState({ size: `${(fraction * 100).toFixed(2)}%` }); + } else { + this.setState({ size: `${adjustedWidth}px` }); + } + } else { + const growsDownwards = splitterPosition === 'end'; + const originalHeight = originalValue.outerSize.height; + const adjustedHeight = growsDownwards + ? Math.max(0, originalHeight + dy) + : Math.max(0, originalHeight - dy); + if (percent) { + const fraction = adjustedHeight / originalValue.parentSize.height; + this.setState({ size: `${(fraction * 100).toFixed(2)}%` }); + } else { + this.setState({ size: `${adjustedHeight}px` }); + } + } + }; + + override componentDidMount() { + this.setState({ + size: this.props.initialSize, + }); + } + + _getMoveStartState = () => { + const outer = this._outer.current; + const parent = outer?.parentElement; + if (!outer || !parent) { + throw new Error('Dragging splitter before ref is known, or no parent?'); + } + + const outerRect = outer.getBoundingClientRect(); + const parentRect = parent.getBoundingClientRect(); + return { + outerSize: { width: outerRect.width, height: outerRect.height }, + parentSize: { width: parentRect.width, height: parentRect.height }, + }; + }; + + _getOrientClassName() { + return this.props.controlledProperty.endsWith('width') + ? 'resizesWidth' + : 'resizesHeight'; + } + + override render() { + const { splitterPosition, controlledProperty, className, children } = + this.props; + const { size } = this.state; + const orientClassName = this._getOrientClassName(); + const splitter = ( + + ); + return ( +
+ {splitterPosition === 'start' ? splitter : null} +
{children}
+ {splitterPosition === 'end' ? splitter : null} +
+ ); + } +} diff --git a/src/components/shared/chart/Viewport.tsx b/src/components/shared/chart/Viewport.tsx index 4471702639..8d732deeda 100644 --- a/src/components/shared/chart/Viewport.tsx +++ b/src/components/shared/chart/Viewport.tsx @@ -6,10 +6,7 @@ import * as React from 'react'; import classNames from 'classnames'; import explicitConnect from 'firefox-profiler/utils/connect'; import { getResizeObserverWrapper } from 'firefox-profiler/utils/resize-observer-wrapper'; -import { - getHasZoomedViaMousewheel, - getPanelLayoutGeneration, -} from 'firefox-profiler/selectors/app'; +import { getHasZoomedViaMousewheel } from 'firefox-profiler/selectors/app'; import { setHasZoomedViaMousewheel } from 'firefox-profiler/actions/app'; import { updatePreviewSelection } from 'firefox-profiler/actions/profile-view'; @@ -132,7 +129,6 @@ export type Viewport = { }; type ChartViewportImplStateProps = { - readonly panelLayoutGeneration: number; readonly hasZoomedViaMousewheel?: boolean; }; @@ -316,10 +312,6 @@ class ChartViewportImpl extends React.PureComponent< this.setState({ horizontalViewport, }); - } else if ( - this.props.panelLayoutGeneration !== newProps.panelLayoutGeneration - ) { - this._setSizeNextFrame(); } } @@ -871,7 +863,6 @@ export function withChartViewport( ChartViewportImplDispatchProps >({ mapStateToProps: (state) => ({ - panelLayoutGeneration: getPanelLayoutGeneration(state), hasZoomedViaMousewheel: getHasZoomedViaMousewheel(state), }), mapDispatchToProps: { setHasZoomedViaMousewheel, updatePreviewSelection }, diff --git a/src/components/timeline/FullTimeline.tsx b/src/components/timeline/FullTimeline.tsx index 97eb3b65d3..cf1729f8e6 100644 --- a/src/components/timeline/FullTimeline.tsx +++ b/src/components/timeline/FullTimeline.tsx @@ -21,7 +21,6 @@ import { getGlobalTrackReferences, getTrackCount, getGlobalTrackOrder, - getPanelLayoutGeneration, } from 'firefox-profiler/selectors'; import { TimelineTrackContextMenu } from './TrackContextMenu'; @@ -57,7 +56,6 @@ type StateProps = { readonly globalTracks: GlobalTrack[]; readonly globalTrackOrder: TrackIndex[]; readonly globalTrackReferences: GlobalTrackReference[]; - readonly panelLayoutGeneration: number; readonly zeroAt: Milliseconds; readonly profileTimelineUnit: TimelineUnit; readonly trackCount: TrackCount; @@ -145,7 +143,6 @@ class FullTimelineImpl extends React.PureComponent { profileTimelineUnit, width, globalTrackReferences, - panelLayoutGeneration, trackCount, changeRightClickedTrack, innerElementRef, @@ -173,7 +170,6 @@ class FullTimelineImpl extends React.PureComponent {
{
diff --git a/src/components/timeline/Selection.css b/src/components/timeline/Selection.css index 47c1bbb72c..9d749915a1 100644 --- a/src/components/timeline/Selection.css +++ b/src/components/timeline/Selection.css @@ -27,6 +27,7 @@ position: relative; display: flex; width: var(--content-width); + min-height: 0; flex: 1; flex-direction: column; margin-left: var(--thread-label-column-width); diff --git a/src/components/timeline/index.css b/src/components/timeline/index.css index 5abd986b91..caa50149b2 100644 --- a/src/components/timeline/index.css +++ b/src/components/timeline/index.css @@ -2,16 +2,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -.timelineOverflowEdgeIndicatorScrollbox { +.timelineOverflowEdgeIndicator > .overflowEdgeIndicatorScrollbox { --internal-background-stop1-color: #eee; --internal-background-stop2-color: var(--grey-30); - position: absolute; + position: relative; /* if the platform scrollbar ends up being larger than --vertical-scrollbar-reserved-width (which is unexpected), make sure there is no horizontal scrollbar */ overflow: hidden auto; width: 100%; - height: 100%; padding-left: var(--thread-label-column-width); margin-right: calc(var(--vertical-scrollbar-reserved-width) * -1); margin-left: calc(var(--thread-label-column-width) * -1); @@ -125,7 +124,6 @@ /* Moving the timeline 1 pixel below the timeline header. This way, the first * visible track's top border will be hidden as expected. */ margin-top: -1px; - margin-bottom: -1px; } @media (forced-colors: active) { @@ -150,7 +148,7 @@ } :root.dark-mode { - .timelineOverflowEdgeIndicatorScrollbox { + .timelineOverflowEdgeIndicator > .overflowEdgeIndicatorScrollbox { --internal-background-stop1-color: var(--grey-80); --internal-background-stop2-color: var(--grey-70); } diff --git a/src/index.tsx b/src/index.tsx index 30658b2910..45a56f62c5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,7 +12,6 @@ import '../res/css/style.css'; import '../res/css/global.css'; import '../res/css/categories.css'; import '../res/css/network.css'; -import 'react-splitter-layout/lib/index.css'; import 'devtools-reps/reps.css'; // React imported for JSX in Root component diff --git a/src/reducers/app.ts b/src/reducers/app.ts index 5c3653dfd9..05d1856400 100644 --- a/src/reducers/app.ts +++ b/src/reducers/app.ts @@ -110,47 +110,6 @@ const isSidebarOpenPerPanel: Reducer = ( } }; -/** - * The panels that make up the timeline, details view, and sidebar can all change - * their sizes depending on the state that is fed to them. In order to control - * the invalidations of this sizing information, provide a "generation" value that - * increases monotonically for any change that potentially changes the sizing of - * any of the panels. This provides a mechanism for subscribing components to - * deterministically update their sizing correctly. - */ -const panelLayoutGeneration: Reducer = (state = 0, action) => { - switch (action.type) { - case 'INCREMENT_PANEL_LAYOUT_GENERATION': - // Sidebar: (fallthrough) - case 'CHANGE_SIDEBAR_OPEN_STATE': - // Timeline: (fallthrough) - case 'HIDE_GLOBAL_TRACK': - case 'SHOW_ALL_TRACKS': - case 'SHOW_PROVIDED_TRACKS': - case 'HIDE_PROVIDED_TRACKS': - case 'SHOW_GLOBAL_TRACK': - case 'SHOW_GLOBAL_TRACK_INCLUDING_LOCAL_TRACKS': - case 'ISOLATE_PROCESS': - case 'ISOLATE_PROCESS_MAIN_THREAD': - case 'HIDE_LOCAL_TRACK': - case 'SHOW_LOCAL_TRACK': - case 'ISOLATE_LOCAL_TRACK': - case 'TOGGLE_RESOURCES_PANEL': - case 'ENABLE_EXPERIMENTAL_CPU_GRAPHS': - case 'ENABLE_EXPERIMENTAL_PROCESS_CPU_TRACKS': - case 'CHANGE_TAB_FILTER': - // Committed range changes: (fallthrough) - case 'COMMIT_RANGE': - case 'POP_COMMITTED_RANGES': - // Bottom box: (fallthrough) - case 'UPDATE_BOTTOM_BOX': - case 'CLOSE_BOTTOM_BOX_FOR_TAB': - return state + 1; - default: - return state; - } -}; - /** * Clicking on tracks can switch between different tabs. This piece of state holds * on to the last relevant thread-based tab that was viewed. This makes the UX nicer @@ -365,7 +324,6 @@ const appStateReducer: Reducer = combineReducers({ urlSetupPhase, hasZoomedViaMousewheel, isSidebarOpenPerPanel, - panelLayoutGeneration, lastVisibleThreadTabSlug, trackThreadHeights, isNewlyPublished, diff --git a/src/selectors/app.tsx b/src/selectors/app.tsx index d46d71eb4e..ec78760d84 100644 --- a/src/selectors/app.tsx +++ b/src/selectors/app.tsx @@ -55,8 +55,6 @@ export const getHasZoomedViaMousewheel: Selector = (state) => { }; export const getIsSidebarOpen: Selector = (state) => getApp(state).isSidebarOpenPerPanel[getSelectedTab(state)]; -export const getPanelLayoutGeneration: Selector = (state) => - getApp(state).panelLayoutGeneration; export const getLastVisibleThreadTabSlug: Selector = (state) => getApp(state).lastVisibleThreadTabSlug; export const getTrackThreadHeights: Selector<{ diff --git a/src/test/components/Viewport.test.tsx b/src/test/components/Viewport.test.tsx index b32f27be94..b0f822007f 100644 --- a/src/test/components/Viewport.test.tsx +++ b/src/test/components/Viewport.test.tsx @@ -17,8 +17,6 @@ import { getPreviewSelection, } from '../../selectors/profile'; -import { changeSidebarOpenState } from '../../actions/app'; - import explicitConnect from '../../utils/connect'; import { ensureExists } from '../../utils/types'; @@ -27,6 +25,7 @@ import { autoMockElementSize, setMockedElementSize, } from '../fixtures/mocks/element-size'; +import { triggerResizeObservers } from '../fixtures/mocks/resize-observer'; import { mockRaf } from '../fixtures/mocks/request-animation-frame'; import { storeWithProfile } from '../fixtures/stores'; import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profile'; @@ -589,8 +588,8 @@ describe('Viewport', function () { }); }); - it('reacts to changes to the panel layout generation', function () { - const { dispatch, getChartViewport, flushRafCalls } = setup(); + it('reacts to container size changes', function () { + const { getChartViewport } = setup(); expect(getChartViewport()).toMatchObject({ containerWidth: BOUNDING_BOX_WIDTH, @@ -606,10 +605,7 @@ describe('Viewport', function () { ...INITIAL_ELEMENT_SIZE, width: BOUNDING_BOX_WIDTH - boundingWidthDiff, }); - act(() => { - dispatch(changeSidebarOpenState('calltree', true)); - }); - flushRafCalls(); + triggerResizeObservers(); expect(getChartViewport()).toMatchObject({ containerWidth: BOUNDING_BOX_WIDTH - boundingWidthDiff, diff --git a/src/test/components/__snapshots__/Timeline.test.tsx.snap b/src/test/components/__snapshots__/Timeline.test.tsx.snap index 4c3ac88f95..204cca2f27 100644 --- a/src/test/components/__snapshots__/Timeline.test.tsx.snap +++ b/src/test/components/__snapshots__/Timeline.test.tsx.snap @@ -51,7 +51,7 @@ exports[`TimelineSelection does not crash when there are no samples or markers 1 class="overflowEdgeIndicatorEdge leftEdge" />
>; - readonly panelLayoutGeneration: number; readonly lastVisibleThreadTabSlug: TabSlug; readonly trackThreadHeights: Record; readonly isNewlyPublished: boolean; diff --git a/yarn.lock b/yarn.lock index bd6dc08b88..8863e5eedb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9363,11 +9363,6 @@ react-redux@^9.2.0: "@types/use-sync-external-store" "^0.0.6" use-sync-external-store "^1.4.0" -react-splitter-layout@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz#70b43ca6a78c056f5e5fbf29c67b597f040fbf2e" - integrity sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA== - react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"