Skip to content

Commit eae93e2

Browse files
committed
hotkeys helpbutton for toggling JSON Debugmode and OSM Power User Mode (Sign in)
1 parent 20b69b7 commit eae93e2

File tree

9 files changed

+271
-22
lines changed

9 files changed

+271
-22
lines changed

src/components/App/App.tsx

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { EquipmentProperties, PlaceProperties } from "@sozialhelden/a11yjson";
2+
import "focus-visible";
3+
import findIndex from "lodash/findIndex";
4+
import includes from "lodash/includes";
5+
import * as React from "react";
6+
import "../../lib/NativeAppStrings";
7+
import { hasBigViewport } from "../../lib/ViewportSize";
8+
import config from "../../lib/config";
9+
import savedState, { saveState } from "../../lib/savedState";
10+
import { isTouchDevice } from "../../lib/userAgent";
11+
import { PlaceDetailsProps } from "../app/PlaceDetailsProps";
12+
import MainView, { UnstyledMainView } from "./MainView";
13+
14+
interface Props extends PlaceDetailsProps {
15+
className?: string;
16+
}
17+
18+
interface State {
19+
isMainMenuOpen: boolean;
20+
isSearchBarVisible: boolean;
21+
isOnSmallViewport: boolean;
22+
isSearchToolbarExpanded: boolean;
23+
}
24+
25+
function isStickySearchBarSupported() {
26+
return hasBigViewport() && !isTouchDevice();
27+
}
28+
29+
class App extends React.Component<Props, State> {
30+
props: Props;
31+
32+
state: State = {
33+
lat: null,
34+
lon: null,
35+
isSpecificLatLonProvided: false,
36+
zoom: null,
37+
isSearchBarVisible: isStickySearchBarSupported(),
38+
isMainMenuOpen: false,
39+
modalNodeState: null,
40+
isOnSmallViewport: false,
41+
isSearchToolbarExpanded: false,
42+
};
43+
44+
map: any;
45+
46+
mainView: UnstyledMainView;
47+
48+
static getDerivedStateFromProps(props: Props, state: State): Partial<State> {
49+
const newState: Partial<State> = {
50+
isSearchToolbarExpanded: false,
51+
isSearchBarVisible: isStickySearchBarSupported(),
52+
};
53+
54+
const parsedZoom =
55+
typeof props.zoom === "string" ? parseInt(props.zoom, 10) : null;
56+
const parsedLat =
57+
typeof props.lat === "string" ? parseFloat(props.lat) : null;
58+
const parsedLon =
59+
typeof props.lon === "string" ? parseFloat(props.lon) : null;
60+
61+
newState.extent = state.extent || props.extent || null;
62+
newState.zoom =
63+
state.zoom ||
64+
parsedZoom ||
65+
Number.parseInt(savedState.map.lastZoom, 10) ||
66+
null;
67+
newState.lat =
68+
state.lat ||
69+
parsedLat ||
70+
(savedState.map.lastCenter &&
71+
Number.parseFloat(savedState.map.lastCenter[0])) ||
72+
null;
73+
newState.lon =
74+
state.lon ||
75+
parsedLon ||
76+
(savedState.map.lastCenter &&
77+
Number.parseFloat(savedState.map.lastCenter[1])) ||
78+
null;
79+
80+
newState.isSpecificLatLonProvided =
81+
Boolean(parsedLat) && Boolean(parsedLon);
82+
83+
return newState;
84+
}
85+
86+
onMoveEnd = (state: Partial<State>) => {
87+
let { zoom, lat, lon } = state;
88+
89+
// Adjust zoom level to be stored in the local storage to make sure the user can
90+
// see some places when reloading the app after some time.
91+
const lastZoom = String(
92+
Math.max(
93+
zoom || 0,
94+
config.minZoomWithSetCategory,
95+
config.minZoomWithoutSetCategory
96+
)
97+
);
98+
99+
saveState({
100+
"map.lastZoom": lastZoom,
101+
"map.lastCenter.lat": String(lat),
102+
"map.lastCenter.lon": String(lon),
103+
"map.lastMoveDate": new Date().toString(),
104+
});
105+
106+
this.setState({ extent: null, lat, lon, zoom });
107+
};
108+
109+
onMapClick = () => {
110+
if (this.state.isSearchToolbarExpanded) {
111+
this.closeSearch();
112+
this.mainView && this.mainView.focusMap();
113+
}
114+
};
115+
116+
showSelectedFeature = (
117+
featureId: string | number,
118+
properties?: PlaceProperties | EquipmentProperties | null
119+
) => {
120+
if (!featureId) {
121+
debugger;
122+
}
123+
const featureIdString = featureId.toString();
124+
const { routerHistory } = this.props;
125+
126+
// show equipment inside their place details
127+
let routeName = "placeDetail";
128+
const params = this.getCurrentParams() as any;
129+
130+
params.id = featureIdString;
131+
delete params.eid;
132+
133+
if (
134+
["elevator", "escalator"].includes(properties.category) &&
135+
properties.placeInfoId
136+
) {
137+
const placeInfoId = properties.placeInfoId;
138+
if (includes(["elevator", "escalator"], properties.category)) {
139+
routeName = "equipment";
140+
params.id = placeInfoId;
141+
params.eid = featureIdString;
142+
}
143+
}
144+
145+
let activeCluster = null;
146+
if (this.state.activeCluster) {
147+
const index = findIndex(
148+
this.state.activeCluster.features,
149+
// @ts-ignore
150+
(f) => (f.id || f._id) === featureIdString
151+
);
152+
activeCluster = index !== -1 ? this.state.activeCluster : null;
153+
}
154+
155+
this.setState({ activeCluster }, () => {
156+
routerHistory.push(routeName, params);
157+
});
158+
};
159+
160+
render() {
161+
const { isSpecificLatLonProvided } = this.state;
162+
const isNodeRoute = Boolean(this.props.featureId);
163+
const mapMoveDate = savedState.map.lastMoveDate;
164+
const wasMapMovedRecently =
165+
mapMoveDate && +new Date() - +mapMoveDate < config.locateTimeout;
166+
const shouldLocateOnStart =
167+
!isSpecificLatLonProvided && !isNodeRoute && !wasMapMovedRecently;
168+
const isSearchBarVisible = this.state.isSearchBarVisible;
169+
170+
return (
171+
<>
172+
<GlobalStyle />
173+
<MainView
174+
{...extraProps}
175+
ref={(mainView) => {
176+
this.mainView = mainView;
177+
}}
178+
/>
179+
</>
180+
);
181+
}
182+
}
183+
184+
export default App;

src/components/App/Layout.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { ToastContainer } from "react-toastify";
88
import "react-toastify/dist/ReactToastify.css";
99
import styled from "styled-components";
1010
import { AppContext } from "../../lib/context/AppContext";
11-
import LoadableMapView from "../MapNew/LoadableMapView";
1211
import GlobalStyle from "./GlobalAppStyle";
1312
import HeadMetaTags from "./HeadMetaTags";
1413
import MainMenu from "./MainMenu/MainMenu";
@@ -64,9 +63,11 @@ export default function Layout({
6463
/>
6564
)}
6665

67-
<main style={{ height: "100%" }} ref={containerRef}>
68-
<LoadableMapView {...{ containerRef }} />
69-
66+
<main
67+
style={{ height: "100%" }}
68+
ref={containerRef}
69+
>
70+
<MapView {...{ containerRef }} />
7071
<BlurLayer active={blur} style={{ zIndex: 1000 }} />
7172
<div style={{ zIndex: 2000 }}>{children}</div>
7273
<ToastContainer position="bottom-center" />

src/components/App/MainMenu/AppLinks.tsx

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { useHotkeys } from "@blueprintjs/core";
12
import Link from "next/link";
2-
import React from "react";
3+
import { useMemo, useState } from "react";
34
import styled from "styled-components";
45
import colors from "../../../lib/colors";
56
import { useCurrentApp } from "../../../lib/context/AppContext";
@@ -50,6 +51,19 @@ export default function AppLinks(props: {}) {
5051
related: { appLinks },
5152
} = app;
5253

54+
55+
const [toogle, setToogle] = useState(false);
56+
const hotkeys = useMemo(() => [
57+
{
58+
combo: "l",
59+
global: true,
60+
label: "Toogle OSM Power User Mode",
61+
onKeyDown: () => setToogle(!toogle),
62+
},
63+
64+
], [toogle]);
65+
const { handleKeyDown, handleKeyUp } = useHotkeys(hotkeys);
66+
5367
const links = Object.values(appLinks)
5468
.sort((a, b) => (a.order || 0) - (b.order || 0))
5569
.map((link) => {
@@ -89,10 +103,12 @@ export default function AppLinks(props: {}) {
89103
if (isEventsLink) {
90104
return <JoinedEventLink {...{ label, url }} key="joined-event" />;
91105
}
92-
106+
93107
const isSessionLink = link.tags && link.tags.indexOf("session") !== -1;
94108
if (isSessionLink) {
95-
return <SessionLink {...{ label }} key="session" className={className} />;
109+
return (
110+
toogle && <SessionLink {...{ label }} key="session" className={className} onKeyDown={handleKeyDown} />
111+
);
96112
}
97113

98114
if (typeof url === "string") {
@@ -108,4 +124,4 @@ export default function AppLinks(props: {}) {
108124
});
109125

110126
return <>{links}</>;
111-
}
127+
}

src/components/CombinedFeaturePanel/CombinedFeaturePanel.tsx

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { useHotkeys } from "@blueprintjs/core";
12
import { uniqBy } from "lodash";
3+
import { useMemo, useState } from "react";
24
import styled from "styled-components";
35
import colors from "../../lib/colors";
46
import {
@@ -7,9 +9,9 @@ import {
79
isOSMFeature,
810
isSearchResultFeature
911
} from "../../lib/model/shared/AnyFeature";
10-
import FeaturesDebugJSON from "./components/FeaturesDebugJSON";
1112
import OSMBuildingDetails from "./OSMBuildingDetails";
1213
import OSMSidewalkDetails from "./OSMSidewalkDetails";
14+
import FeaturesDebugJSON from "./components/FeaturesDebugJSON";
1315
import PlaceOfInterestDetails from "./type-specific/poi/PlaceOfInterestDetails";
1416

1517
type Props = {
@@ -46,9 +48,21 @@ export function CombinedFeaturePanel(props: Props) {
4648
isSearchResultFeature(feature) ? feature.properties.osm_id : feature._id
4749
);
4850

51+
/* Hotkeys */
52+
const [toogle, setToogle] = useState(false);
53+
const hotkeys = useMemo(() => [
54+
{
55+
combo: "j",
56+
global: true,
57+
label: "Show JSON Feature Debugger",
58+
onKeyDown: () => setToogle(!toogle),
59+
},
60+
61+
], [toogle]);
62+
const { handleKeyDown, handleKeyUp } = useHotkeys(hotkeys);
4963

5064
return (
51-
<Panel>
65+
<Panel onKeyDown={handleKeyDown} >
5266
{features && features[0] && (
5367
<>
5468
<PlaceOfInterestDetails feature={features[0]} />
@@ -65,9 +79,11 @@ export function CombinedFeaturePanel(props: Props) {
6579
features
6680
.slice(1)
6781
.map((feature) => <FeatureSection key={getKey(feature)} feature={feature} />)}
82+
6883
<p>
69-
<FeaturesDebugJSON features={features} />
84+
{toogle && <FeaturesDebugJSON features={features} /> }
7085
</p>
71-
</Panel>
86+
</Panel>
7287
);
7388
}
89+

src/components/CombinedFeaturePanel/components/FeatureAccessibility.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type Props = {
99
const Card = styled.section`
1010
background-color: white;
1111
padding: 0.25rem 0.5rem;
12-
box-shadow: 0 0.125rem 0.125rem rgba(0, 0, 0, 0.1);
12+
box-shadow: 0 0.125rem 1.125rem rgba(0, 0, 0, 0.1);
1313
border-radius: 0.25rem;
1414
margin: 0 -0.25rem;
1515
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {
2+
Button,
3+
HotkeysContext
4+
} from '@blueprintjs/core';
5+
import React from "react";
6+
7+
export default function HelpButton() {
8+
const [, dispatch] = React.useContext(HotkeysContext);
9+
const openHelp = React.useCallback(() => {
10+
dispatch({ type: 'OPEN_DIALOG' });
11+
}, [dispatch]);
12+
return (
13+
<Button
14+
text="?"
15+
large
16+
onClick={openHelp}
17+
style={{
18+
position: 'fixed', left: '1rem', bottom: '1rem', borderRadius: '50%', fontSize: '1rem', zIndex: 10,
19+
}}
20+
/>
21+
);
22+
}
23+

src/components/MapNew/MapView.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { useRouter } from "next/router";
33
import * as React from "react";
44
import { useCallback, useLayoutEffect, useRef, useState } from "react";
55
import {
6-
Layer, Map, MapProvider, MapRef,
6+
Layer,
7+
Map,
8+
MapProvider,
9+
MapRef,
710
NavigationControl,
811
Source,
912
ViewState,
@@ -17,6 +20,7 @@ import { uniq } from "lodash";
1720
import "mapbox-gl/dist/mapbox-gl.css";
1821
import { createGlobalStyle } from "styled-components";
1922
import getFeatureIdsFromLocation from "../../lib/model/shared/getFeatureIdsFromLocation";
23+
import HelpButton from "../CombinedFeaturePanel/components/HelpButton";
2024
import { databaseTableNames, filterLayers } from "./filterLayers";
2125
import useMapStyle from "./useMapStyle";
2226

@@ -225,6 +229,7 @@ export default function MapView(props: IProps) {
225229
<NavigationControl style={{ right: "1rem", top: "1rem" }} />
226230
</Map>
227231
</MapProvider>
232+
<HelpButton />
228233
</>
229234
);
230235
}

src/components/Session/SessionLink.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
import { Spinner } from "@blueprintjs/core"
3-
import { useSession } from "next-auth/react"
2+
import { Spinner } from "@blueprintjs/core";
3+
import { useSession } from "next-auth/react";
44
import Link from "next/link";
55
import { t } from "ttag";
66
import { UserIcon } from "../icons/ui-elements";
@@ -27,4 +27,5 @@ export default function SessionLink({ label, className }: { label: string, class
2727
return <Link href={"/me"} className={className}>
2828
{t`Sign in`}
2929
</Link>
30-
}
30+
}
31+

0 commit comments

Comments
 (0)