Skip to content

Commit

Permalink
Add an open/close button for the Sidebar (Merge PR firefox-devtools#1096
Browse files Browse the repository at this point in the history
)

This patch adds an open/close button to control the display of a Sidebar. It's closed by default (and still disabled currently anyway).

This also makes the layout more shrinkable, when reducing the browser window's width.
  • Loading branch information
julienw authored Jul 13, 2018
2 parents 537d58e + a17e04e commit d41b8af
Show file tree
Hide file tree
Showing 16 changed files with 481 additions and 35 deletions.
49 changes: 49 additions & 0 deletions res/css/photon-components.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.photon-button {
/* reset default styles */
background: none;
border: none;
margin: 0;
padding: 0;

/* photon styles */
background-color: var(--grey-90-a10);
padding: 0 8px;
border-radius: 2px;
}

.photon-button:hover {
background-color: var(--grey-90-a20);
}

.photon-button:hover:active {
background-color: var(--grey-90-a30);
}

/* This is a Firefox-specific style because Firefox adds a focusring at a bad
* position. We're adding our own below. */
.photon-button::-moz-focus-inner {
border: none;
}

/* Using -moz-focusring to target Firefox only, as a result of the style above */
.photon-button:-moz-focusring {
outline: 1px dotted black;

/* This should be eventually the default behavior.
* See https://bugzilla.mozilla.org/show_bug.cgi?id=315209 */
-moz-outline-radius: 2px;
}

.photon-button-ghost {
background-color: transparent;
height: 32px;
width: 32px;
}

.photon-button-ghost:hover {
background-color: var(--grey-90-a10);
}

.photon-button-ghost:hover:active {
background-color: var(--grey-90-a20);
}
8 changes: 6 additions & 2 deletions res/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ html, body {
body, #root, .profileViewer {
display: flex;
flex: 1;
min-width: 0; /* This allows Flexible Layout to shrink this further than its min-content */
}

.profileViewer {
Expand Down Expand Up @@ -602,20 +603,21 @@ body, #root, .profileViewer {
.tabBarContainer {
display: flex;
flex-flow: row nowrap;
justify-content: space-between; /* This pushes the sidebar button to the right */
align-items: center;
background: var(--grey-10);
position: relative;
border: solid var(--grey-30);
border-width: 1px 0 1px;
}

.tabBarTabWrapper {
margin: 0;
padding: 0 0.5px;
list-style: none;
display: flex;
flex-flow: row nowrap;
margin: 0 -1px;
max-width: 100%;
min-width: 0; /* This makes the tab container actually shrinkable below min-content */
}

.tabBarTab {
Expand All @@ -633,6 +635,8 @@ body, #root, .profileViewer {
position: relative;
border: solid transparent;
border-width: 0 1px 0 1px;
overflow: hidden;
text-overflow: ellipsis;
}
.tabBarTab.selected {
background: #fff;
Expand Down
7 changes: 7 additions & 0 deletions res/img/svg/pane-collapse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions res/img/svg/pane-expand.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/actions/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export function show404(url: string): Action {
return { type: 'ROUTE_NOT_FOUND', url };
}

export function changeSidebarOpenState(tab: TabSlug, isOpen: boolean): Action {
return { type: 'CHANGE_SIDEBAR_OPEN_STATE', tab, isOpen };
}

/**
* This function is called when a browser navigation event happens. A new UrlState
* is generated when the window.location is serialized, or the state is pulled out of
Expand Down
19 changes: 19 additions & 0 deletions src/components/app/Details.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,22 @@
display: flex;
flex-direction: column;
}

.sidebar-open-close-button {
flex: none;

height: 24px;
width: 24px;
margin: 4px;

background-position: center;
background-repeat: no-repeat;
}

.sidebar-open-close-button-isopen {
background-image: url(../../../res/img/svg/pane-collapse.svg);
}

.sidebar-open-close-button-isclosed {
background-image: url(../../../res/img/svg/pane-expand.svg);
}
44 changes: 41 additions & 3 deletions src/components/app/Details.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,25 @@
// @flow

import React, { PureComponent } from 'react';
import classNames from 'classnames';

import explicitConnect from '../../utils/connect';
import TabBar from './TabBar';
import ProfileCallTreeView from '../calltree/ProfileCallTreeView';
import MarkerTable from '../marker-table';
import StackChart from '../stack-chart/';
import MarkerChart from '../marker-chart/';
import FlameGraph from '../flame-graph/';
import { changeSelectedTab, changeTabOrder } from '../../actions/app';
import selectSidebar from '../sidebar';

import {
changeSelectedTab,
changeTabOrder,
changeSidebarOpenState,
} from '../../actions/app';
import { getTabOrder } from '../../reducers/profile-view';
import { getSelectedTab } from '../../reducers/url-state';
import { getIsSidebarOpen } from '../../reducers/app';
import CallNodeContextMenu from '../shared/CallNodeContextMenu';
import MarkerTableContextMenu from '../marker-table/ContextMenu';
import ProfileThreadHeaderContextMenu from '../header/ProfileThreadHeaderContextMenu';
Expand All @@ -27,16 +36,19 @@ import type {
} from '../../utils/connect';
import type { TabSlug } from '../../app-logic/tabs-handling';

require('./Details.css');
import '../../../res/css/photon-components.css';
import './Details.css';

type StateProps = {|
+tabOrder: number[],
+selectedTab: TabSlug,
+isSidebarOpen: boolean,
|};

type DispatchProps = {|
+changeSelectedTab: typeof changeSelectedTab,
+changeTabOrder: typeof changeTabOrder,
+changeSidebarOpenState: typeof changeSidebarOpenState,
|};

type Props = ConnectedProps<{||}, StateProps, DispatchProps>;
Expand All @@ -51,8 +63,31 @@ class ProfileViewer extends PureComponent<Props> {
changeSelectedTab(tabSlug);
};

_onClickSidebarButton = () => {
const { selectedTab, isSidebarOpen, changeSidebarOpenState } = this.props;
changeSidebarOpenState(selectedTab, !isSidebarOpen);
};

render() {
const { tabOrder, changeTabOrder, selectedTab } = this.props;
const { tabOrder, selectedTab, isSidebarOpen, changeTabOrder } = this.props;
const hasSidebar = selectSidebar(selectedTab) !== null;
const extraButton = hasSidebar && (
<button
className={classNames(
'sidebar-open-close-button',
'photon-button',
'photon-button-ghost',
{
'sidebar-open-close-button-isopen': isSidebarOpen,
'sidebar-open-close-button-isclosed': !isSidebarOpen,
}
)}
title={isSidebarOpen ? 'Close the sidebar' : 'Open the sidebar'}
type="button"
onClick={this._onClickSidebarButton}
/>
);

return (
<div className="Details">
<TabBar
Expand All @@ -61,6 +96,7 @@ class ProfileViewer extends PureComponent<Props> {
tabOrder={tabOrder}
onSelectTab={this._onSelectTab}
onChangeTabOrder={changeTabOrder}
extraElements={extraButton}
/>
{
{
Expand All @@ -84,10 +120,12 @@ const options: ExplicitConnectOptions<{||}, StateProps, DispatchProps> = {
mapStateToProps: state => ({
tabOrder: getTabOrder(state),
selectedTab: getSelectedTab(state),
isSidebarOpen: getIsSidebarOpen(state),
}),
mapDispatchToProps: {
changeSelectedTab,
changeTabOrder,
changeSidebarOpenState,
},
component: ProfileViewer,
};
Expand Down
7 changes: 5 additions & 2 deletions src/components/app/DetailsContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Details from './Details';
import selectSidebar from '../sidebar';

import { getSelectedTab } from '../../reducers/url-state';
import { getIsSidebarOpen } from '../../reducers/app';
import explicitConnect from '../../utils/connect';

import type { TabSlug } from '../../app-logic/tabs-handling';
Expand All @@ -24,10 +25,11 @@ function dispatchResizeEvent() {

type StateProps = {|
+selectedTab: TabSlug,
+isSidebarOpen: boolean,
|};

function DetailsContainer({ selectedTab }: StateProps) {
const Sidebar = selectSidebar(selectedTab);
function DetailsContainer({ selectedTab, isSidebarOpen }: StateProps) {
const Sidebar = isSidebarOpen && selectSidebar(selectedTab);

return (
<SplitterLayout
Expand All @@ -45,6 +47,7 @@ function DetailsContainer({ selectedTab }: StateProps) {
const options: ExplicitConnectOptions<{||}, StateProps, {||}> = {
mapStateToProps: state => ({
selectedTab: getSelectedTab(state),
isSidebarOpen: getIsSidebarOpen(state),
}),
component: DetailsContainer,
};
Expand Down
7 changes: 5 additions & 2 deletions src/components/app/TabBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// @flow

import React, { PureComponent } from 'react';
import * as React from 'react';
import classNames from 'classnames';
import Reorderable from '../shared/Reorderable';

Expand All @@ -18,9 +18,10 @@ type Props = {|
+tabOrder: number[],
+onSelectTab: string => void,
+onChangeTabOrder: (number[]) => Action,
+extraElements?: React.Node,
|};

class TabBar extends PureComponent<Props> {
class TabBar extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
(this: any)._mouseDownListener = this._mouseDownListener.bind(this);
Expand All @@ -40,6 +41,7 @@ class TabBar extends PureComponent<Props> {
selectedTabName,
tabOrder,
onChangeTabOrder,
extraElements,
} = this.props;
return (
<div className={classNames('tabBarContainer', className)}>
Expand All @@ -63,6 +65,7 @@ class TabBar extends PureComponent<Props> {
</li>
))}
</Reorderable>
{extraElements}
</div>
);
}
Expand Down
42 changes: 40 additions & 2 deletions src/reducers/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
// @flow
import { combineReducers } from 'redux';

import { getSelectedTab } from './url-state';
import { tabSlugs } from '../app-logic/tabs-handling';

import type { Action } from '../types/store';
import type { State, AppState, AppViewState, Reducer } from '../types/reducers';
import type {
State,
AppState,
AppViewState,
IsSidebarOpenPerPanelState,
Reducer,
} from '../types/reducers';

function view(
state: AppViewState = { phase: 'INITIALIZING' },
Expand Down Expand Up @@ -63,10 +72,37 @@ function hasZoomedViaMousewheel(state: boolean = false, action: Action) {
return state;
}
}

function isSidebarOpenPerPanel(
state: IsSidebarOpenPerPanelState,
action: Action
): IsSidebarOpenPerPanelState {
if (state === undefined) {
state = {};
tabSlugs.forEach(tabSlug => (state[tabSlug] = false));
}

switch (action.type) {
case 'CHANGE_SIDEBAR_OPEN_STATE': {
const { tab, isOpen } = action;
// Due to how this action will be dispatched we'll always have the value
// changed so we don't need the performance optimization of checking the
// stored value against the new value.
return {
...state,
[tab]: isOpen,
};
}
default:
return state;
}
}

const appStateReducer: Reducer<AppState> = combineReducers({
view,
isUrlSetupDone,
hasZoomedViaMousewheel,
isSidebarOpenPerPanel,
});

export default appStateReducer;
Expand All @@ -75,6 +111,8 @@ export const getApp = (state: State): AppState => state.app;
export const getView = (state: State): AppViewState => getApp(state).view;
export const getIsUrlSetupDone = (state: State): boolean =>
getApp(state).isUrlSetupDone;
export const getHasZoomedViaMousewheel = (state: Object): boolean => {
export const getHasZoomedViaMousewheel = (state: State): boolean => {
return getApp(state).hasZoomedViaMousewheel;
};
export const getIsSidebarOpen = (state: State): boolean =>
getApp(state).isSidebarOpenPerPanel[getSelectedTab(state)];
Loading

0 comments on commit d41b8af

Please sign in to comment.