diff --git a/src/components/views/beacon/BeaconViewDialog.tsx b/src/components/views/beacon/BeaconViewDialog.tsx index 17ba972ef9..a2fa704e8e 100644 --- a/src/components/views/beacon/BeaconViewDialog.tsx +++ b/src/components/views/beacon/BeaconViewDialog.tsx @@ -125,6 +125,9 @@ const BeaconViewDialog: React.FC = ({ initialFocusedBeacon, roomId, matr setFocusedBeaconState({ beacon, ts: Date.now() }); }; + const hasOwnBeacon = + liveBeacons.filter((beacon) => beacon?.beaconInfoOwner === matrixClient.getUserId()).length > 0; + return ( @@ -136,6 +139,7 @@ const BeaconViewDialog: React.FC = ({ initialFocusedBeacon, roomId, matr interactive onError={setMapDisplayError} className="mx_BeaconViewDialog_map" + allowGeolocate={!hasOwnBeacon} > {({ map }: { map: maplibregl.Map }) => ( <> diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 838fa619da..57a65d0419 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -23,10 +23,9 @@ import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/client"; import { _t } from "../../../languageHandler"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import Modal from "../../../Modal"; -import SdkConfig from "../../../SdkConfig"; import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils"; import { GenericPosition, genericPositionFromGeolocation, getGeoUri } from "../../../utils/beacon"; -import { LocationShareError, findMapStyleUrl } from "../../../utils/location"; +import { LocationShareError, findMapStyleUrl, positionFailureMessage } from "../../../utils/location"; import ErrorDialog from "../dialogs/ErrorDialog"; import AccessibleButton from "../elements/AccessibleButton"; import { MapError } from "./MapError"; @@ -266,21 +265,3 @@ class LocationPicker extends React.Component { } export default LocationPicker; - -function positionFailureMessage(code: number): string { - const brand = SdkConfig.get().brand; - switch (code) { - case 1: - return _t( - "%(brand)s was denied permission to fetch your location. " + - "Please allow location access in your browser settings.", - { brand }, - ); - case 2: - return _t("Failed to fetch your location. Please try again later."); - case 3: - return _t("Timed out trying to fetch your location. Please try again later."); - case 4: - return _t("Unknown error fetching location. Please try again later."); - } -} diff --git a/src/components/views/location/LocationViewDialog.tsx b/src/components/views/location/LocationViewDialog.tsx index 48a4b78e1b..3e706d3df2 100644 --- a/src/components/views/location/LocationViewDialog.tsx +++ b/src/components/views/location/LocationViewDialog.tsx @@ -68,6 +68,7 @@ export default class LocationViewDialog extends React.Component onError={this.onError} interactive className="mx_LocationViewDialog_map" + allowGeolocate={true} > {({ map }) => ( <> diff --git a/src/components/views/location/Map.tsx b/src/components/views/location/Map.tsx index 87666f022b..a6376039db 100644 --- a/src/components/views/location/Map.tsx +++ b/src/components/views/location/Map.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode, useContext, useEffect } from "react"; +import React, { ReactNode, useContext, useEffect, useState } from "react"; import classNames from "classnames"; import * as maplibregl from "maplibre-gl"; import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix"; @@ -22,10 +22,13 @@ import { logger } from "matrix-js-sdk/src/logger"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import { parseGeoUri } from "../../../utils/location"; +import { parseGeoUri, positionFailureMessage } from "../../../utils/location"; import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils"; import { useMap } from "../../../utils/location/useMap"; import { Bounds } from "../../../utils/beacon/bounds"; +import Modal from "../../../Modal"; +import ErrorDialog from "../dialogs/ErrorDialog"; +import { _t } from "../../../languageHandler"; const useMapWithStyle = ({ id, @@ -33,12 +36,14 @@ const useMapWithStyle = ({ onError, interactive, bounds, + allowGeolocate, }: { id: string; centerGeoUri?: string; + onError(error: Error): void; interactive?: boolean; bounds?: Bounds; - onError(error: Error): void; + allowGeolocate: boolean; }): { map: maplibregl.Map; bodyId: string; @@ -86,12 +91,41 @@ const useMapWithStyle = ({ } }, [map, bounds]); + const [geolocate] = useState( + allowGeolocate + ? new maplibregl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true, + }, + trackUserLocation: false, + }) + : null, + ); + + useEffect(() => { + if (map && geolocate) { + map.addControl(geolocate); + geolocate.on("error", onGeolocateError); + return () => { + geolocate.off("error", onGeolocateError); + }; + } + }, [map, geolocate]); + return { map, bodyId, }; }; +const onGeolocateError = (e: GeolocationPositionError): void => { + logger.error("Could not fetch location", e); + Modal.createDialog(ErrorDialog, { + title: _t("Could not fetch location"), + description: positionFailureMessage(e.code), + }); +}; + interface MapProps { id: string; interactive?: boolean; @@ -105,13 +139,24 @@ interface MapProps { centerGeoUri?: string; bounds?: Bounds; className?: string; + allowGeolocate?: boolean; onClick?: () => void; onError?: (error: Error) => void; children?: (renderProps: { map: maplibregl.Map }) => ReactNode; } -const Map: React.FC = ({ bounds, centerGeoUri, children, className, id, interactive, onError, onClick }) => { - const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds }); +const Map: React.FC = ({ + bounds, + centerGeoUri, + children, + className, + allowGeolocate, + id, + interactive, + onError, + onClick, +}) => { + const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds, allowGeolocate }); const onMapClick = (event: React.MouseEvent): void => { // Eat click events when clicking the attribution button diff --git a/src/utils/location/index.ts b/src/utils/location/index.ts index f94c6a12dd..035fe52694 100644 --- a/src/utils/location/index.ts +++ b/src/utils/location/index.ts @@ -20,3 +20,4 @@ export * from "./locationEventGeoUri"; export * from "./LocationShareErrors"; export * from "./map"; export * from "./parseGeoUri"; +export * from "./positionFailureMessage"; diff --git a/src/utils/location/positionFailureMessage.ts b/src/utils/location/positionFailureMessage.ts new file mode 100644 index 0000000000..a1e2711208 --- /dev/null +++ b/src/utils/location/positionFailureMessage.ts @@ -0,0 +1,36 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { _t } from "../../languageHandler"; +import SdkConfig from "../../SdkConfig"; + +export const positionFailureMessage = (code: number): string => { + const brand = SdkConfig.get().brand; + switch (code) { + case 1: + return _t( + "%(brand)s was denied permission to fetch your location. " + + "Please allow location access in your browser settings.", + { brand }, + ); + case 2: + return _t("Failed to fetch your location. Please try again later."); + case 3: + return _t("Timed out trying to fetch your location. Please try again later."); + case 4: + return _t("Unknown error fetching location. Please try again later."); + } +};