Skip to content

Commit

Permalink
Add a preferences field to select the position of the info panel.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmurray committed Nov 3, 2023
1 parent f0f538e commit 225086b
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 35 deletions.
3 changes: 3 additions & 0 deletions locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
"secondAbbr": "sec",
"Max": "Max",
"Min": "Min",
"Left": "Left",
"Right": "Right",
"rangeTo": "-",
"Total": "Total",
"Calendar": "Calendar",
Expand Down Expand Up @@ -256,6 +258,7 @@
"ResetToDefault": "Reset preference to default value",
"General": "General preferences",
"DefaultSection": "Default section at opening",
"InfoPanelPosition": "Info panel position",
"DefaultColor": "Default color",
"DefaultWalkingSpeedKph": "Default walking speed (km/h)",
"DefaultWalkingSpeedKphHelp": "Walking speed varies according to age and gender between 3 km/h (elderly people and young children) and 7 km/h (young and/or very active people). Men are on average a little faster than women and in general, walking speed decreases with age. The speed of a person in a manual wheelchair varies between 2 and 3 km/h depending on physical strength.",
Expand Down
3 changes: 3 additions & 0 deletions locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
"secondAbbr": "sec",
"Max": "Max",
"Min": "Min",
"Left": "Gauche",
"Right": "Droite",
"rangeTo": " à ",
"Total": "Total",
"Calendar": "Calendrier",
Expand Down Expand Up @@ -256,6 +258,7 @@
"ResetToDefault": "Réinitialiser cette préférence à sa valeur par défaut",
"General": "Préférences générales",
"DefaultSection": "Section par défaut lors de l'ouverture",
"InfoPanelPosition": "Position du panneau d'information",
"DefaultColor": "Couleur par défaut",
"DefaultWalkingSpeedKph": "Vitesse de marche par défaut (km/h)",
"DefaultWalkingSpeedKphHelp": "La vitesse de marche varie selon l'âge et le genre entre 3 km/h (personnes âges et jeunes enfants) et 7 km/h (personnes jeunes et/ou très actives). Les hommes sont en moyenne un peu plus rapides que les femmes et de manière générale, la vitesse de marche diminue avec l'âge. La vitesse d'une personne en chaise roulante manuelle varie entre 2 et 3 km/h selon la force physique.",
Expand Down
1 change: 1 addition & 0 deletions packages/chaire-lib-common/src/config/Preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ export class PreferencesClass extends ObjectWithHistory<PreferencesModelWithIdAn
try {
socket ? await this.updateFromSocket(socket, this.attributes) : await this.updateFromFetch(this.attributes);
eventManager?.emit('preferences.updated');
this._eventEmitter.emit(prefChangeEvent, this._attributes);
} catch (error) {
console.error('Error loading preferences from server');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface SectionDescription {
}
export interface PreferencesModel {
defaultSection: string;
infoPanelPosition: string;
sections: {
[key: string]: {
[key: string]: SectionDescription;
Expand All @@ -33,6 +34,7 @@ export interface PreferencesModel {
// TODO: Type more fields
const defaultPreferences: PreferencesModel = {
defaultSection: 'agencies',
infoPanelPosition: 'right',
sections: {
transition: {
agencies: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,41 @@ import React, { createRef, useEffect, useState } from 'react';

// This components provides a resizable split panel.
// Code largely comes from https://blog.theodo.com/2020/11/react-resizeable-split-panels/
// No need for a props and state about the width of the rightmost component
// No need for props and state about the width of the rightmost component
// because we use flexbox to adjust its width.

interface SplitViewProps {
left?: React.ReactElement;
right?: React.ReactElement;
initialLeftWith: string;
leftViewID?: string;
initialLeftWidth: string;
minLeftWidth?: number;
hideLeftViewWhenResizing?: boolean;
hideRightViewWhenResizing?: boolean;
}

export const SplitView: React.FunctionComponent<SplitViewProps> = ({ left, right, initialLeftWith, minLeftWidth }) => {
/**
* Allows displaying two components side-by-side with a movable divider in the middle to adjust the widths.
* @param leftViewID This is used when switching component positions. If we don't have a unique ID, we can't know when to change the width of the left-view to the width of the previously right-view.
* @returns
*/
export const SplitView: React.FunctionComponent<SplitViewProps> = ({
left,
right,
leftViewID,
initialLeftWidth,
hideLeftViewWhenResizing,
hideRightViewWhenResizing,
minLeftWidth
}) => {
const [leftWidth, setLeftWidth] = useState<number | undefined>();
const [_leftViewID, _setLeftViewID] = useState<string | undefined>(leftViewID);
const [separatorXPosition, setSeparatorXPosition] = useState<number | undefined>();
const [dragging, setDragging] = useState(false);

const splitPaneRef = createRef<HTMLDivElement>();
const leftRef = createRef<HTMLDivElement>();
const rightRef = createRef<HTMLDivElement>();

const onMouseDown = (e: React.MouseEvent) => {
setSeparatorXPosition(e.clientX);
Expand Down Expand Up @@ -86,13 +104,26 @@ export const SplitView: React.FunctionComponent<SplitViewProps> = ({ left, right
}
}, [leftRef, leftWidth, setLeftWidth]);

useEffect(() => {
console.log(leftViewID, _leftViewID);
if (_leftViewID !== leftViewID && leftRef.current && rightRef.current) {
// We now know that the views have been switched (L went to R)
// and thus we can exchange the widths of their DIVs.
setLeftWidth(rightRef.current.offsetWidth);
_setLeftViewID(leftViewID);
}
}, [leftViewID]);

return (
<div className={'tr__resizable-split-view'} ref={splitPaneRef}>
{/* Hide contents when dragging. This reduces flickering since react does not have to redraw at every width change. */}
<div
className="tr__resizable-split-view-left-panel"
ref={leftRef}
style={{ visibility: !dragging ? 'visible' : 'hidden', width: initialLeftWith }}
style={{
visibility: hideLeftViewWhenResizing && dragging ? 'hidden' : 'visible',
width: initialLeftWidth
}}
>
{left}
</div>
Expand All @@ -104,7 +135,13 @@ export const SplitView: React.FunctionComponent<SplitViewProps> = ({ left, right
>
<div className="tr__resizable-split-view-divider" />
</div>
<div className="tr__resizable-split-view-right-panel">{right}</div>
<div
className="tr__resizable-split-view-right-panel"
ref={rightRef}
style={{ visibility: hideRightViewWhenResizing && dragging ? 'hidden' : 'visible' }}
>
{right}
</div>
</div>
);
};
Expand Down
4 changes: 1 addition & 3 deletions packages/chaire-lib-frontend/src/styles/_dashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
}

#tr__left-menu {
position: absolute;
z-index: 20;
left: 0;
flex: 0 4rem;
Expand Down Expand Up @@ -123,9 +122,7 @@
#tr__main-map {
flex: 1;
height: 100%;
margin-left: 4rem;
position: relative;
border-left: 1px solid $border-white;
min-height: 0;
}

Expand Down Expand Up @@ -207,6 +204,7 @@
display: flex;
flex-direction: row;
align-items: flex-start;
border-left: 1px solid $border-white;
}

.tr__resizable-split-view-left-panel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ interface DashboardProps extends WithTranslation {

interface DashboardState {
activeSection: string;
infoPanelPosition: string;
preferencesLoaded: boolean;
socketConnected: boolean;
socketWasConnected: boolean;
Expand Down Expand Up @@ -108,6 +109,7 @@ class Dashboard extends React.Component<DashboardProps, DashboardState> {
socketWasConnected: false,
showFullSizePanel: false,
activeSection,
infoPanelPosition: 'right',
mainMapLayerGroups,
unsavedChangesModalIsOpen: false,
availableRoutingModes: []
Expand All @@ -124,6 +126,8 @@ class Dashboard extends React.Component<DashboardProps, DashboardState> {

serviceLocator.eventManager.emit('progress', { name: 'MapLoading', progress: 0.0 });

Preferences.addChangeListener(this.onPreferencesChange);

const allLayoutContribs = props.contributions.flatMap((contrib) => contrib.getLayoutContributions());
this.contributions = {
bottomPanel: allLayoutContribs.filter((contrib) => contrib.placement === 'bottomPanel'),
Expand Down Expand Up @@ -214,6 +218,11 @@ class Dashboard extends React.Component<DashboardProps, DashboardState> {
}
};

onPreferencesChange = (updates: any) => {
const infoPanelPosition = Preferences.get('infoPanelPosition');
this.setState({ infoPanelPosition });
};

loadLayersAndCollections = () => {
if (!this.state.mainMapLayerGroups.includes('transit')) {
return;
Expand Down Expand Up @@ -265,7 +274,8 @@ class Dashboard extends React.Component<DashboardProps, DashboardState> {
preferencesLoaded: true,
socketConnected: true,
socketWasConnected: true,
activeSection: Preferences.getAttributes().defaultSection
activeSection: Preferences.getAttributes().defaultSection,
infoPanelPosition: Preferences.getAttributes().infoPanelPosition
});
});
}
Expand Down Expand Up @@ -312,6 +322,32 @@ class Dashboard extends React.Component<DashboardProps, DashboardState> {
const mapZoom = preferencesMapZoom || 10;
const Map = this.props.mainMap;

const mapComponent = (
<Map
i18n={this.props.i18n}
t={this.props.t}
tReady={this.props.tReady}
center={mapCenter}
zoom={mapZoom}
activeSection={this.state.activeSection}
>
{this.state.showFullSizePanel && (
<FullSizePanel
activeSection={this.state.activeSection}
contributions={this.contributions.fullSize}
/>
)}
</Map>
);

const infoPanelComponent = (
<RightPanel
activeSection={this.state.activeSection}
contributions={this.contributions.rightPanel}
availableRoutingModes={this.state.availableRoutingModes}
/>
);

return (
<React.Fragment>
{!this.state.socketConnected && this.state.socketWasConnected && (
Expand All @@ -328,32 +364,13 @@ class Dashboard extends React.Component<DashboardProps, DashboardState> {
{/* TODO Should not need to pass the i18n props, anyway, we won't have to pass the Map component as props soon either */}
<LeftMenu activeSection={this.state.activeSection} contributions={this.contributions.menuBar} />
<SplitView
minLeftWidth={500}
initialLeftWith={'65%'}
left={
<Map
i18n={this.props.i18n}
t={this.props.t}
tReady={this.props.tReady}
center={mapCenter}
zoom={mapZoom}
activeSection={this.state.activeSection}
>
{this.state.showFullSizePanel && (
<FullSizePanel
activeSection={this.state.activeSection}
contributions={this.contributions.fullSize}
/>
)}
</Map>
}
right={
<RightPanel
activeSection={this.state.activeSection}
contributions={this.contributions.rightPanel}
availableRoutingModes={this.state.availableRoutingModes}
/>
}
minLeftWidth={this.state.infoPanelPosition === 'left' ? 500 : 150}
initialLeftWidth={this.state.infoPanelPosition === 'right' ? '65%' : '35%'}
leftViewID={this.state.infoPanelPosition} // Just has to be something that changes when we switch the info panel position from R to L.
hideRightViewWhenResizing={this.state.infoPanelPosition === 'left'}
hideLeftViewWhenResizing={this.state.infoPanelPosition === 'right'}
right={this.state.infoPanelPosition === 'right' ? infoPanelComponent : mapComponent}
left={this.state.infoPanelPosition === 'left' ? infoPanelComponent : mapComponent}
/>
{this.state.unsavedChangesModalIsOpen && (
<ConfirmModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ const PreferencesSectionGeneral: React.FunctionComponent<PreferencesSectionProps
preferences={props.preferences}
/>
</InputWrapper>
<InputWrapper label={props.t('main:preferences:InfoPanelPosition')}>
<InputSelect
id={'formFieldPreferencesInfoPanelPosition'}
value={prefs.infoPanelPosition}
choices={[
{
label: props.t('main:Left'),
value: 'left'
},
{
label: props.t('main:Right'),
value: 'right'
}
]}
t={props.t}
onValueChange={(e) => props.onValueChange('infoPanelPosition', { value: e.target.value })}
/>
<PreferencesResetToDefaultButton
resetPrefToDefault={props.resetPrefToDefault}
path="infoPanelPosition"
preferences={props.preferences}
/>
</InputWrapper>

<InputWrapper
label={props.t('main:preferences:DefaultWalkingSpeedKph')}
Expand Down

0 comments on commit 225086b

Please sign in to comment.