From 4572b382031ed2ccfa9be0f98e9e378ae1bb8869 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 25 Jan 2022 10:56:40 +0000 Subject: [PATCH 1/6] Look up tile server info in homeserver's .well-known area --- src/components/views/location/LocationPicker.tsx | 5 ++--- src/components/views/messages/MLocationBody.tsx | 16 +++++++++++++++- src/utils/WellKnownUtils.ts | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index e02879017da..84e76f1793e 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -19,7 +19,6 @@ import maplibregl from 'maplibre-gl'; import { logger } from "matrix-js-sdk/src/logger"; import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; -import SdkConfig from '../../../SdkConfig'; import DialogButtons from "../elements/DialogButtons"; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -27,6 +26,7 @@ import MemberAvatar from '../avatars/MemberAvatar'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; import Modal from '../../../Modal'; import ErrorDialog from '../dialogs/ErrorDialog'; +import { findMapStyleUrl } from '../messages/MLocationBody'; interface IProps { sender: RoomMember; @@ -69,10 +69,9 @@ class LocationPicker extends React.Component { }; componentDidMount() { - const config = SdkConfig.get(); this.map = new maplibregl.Map({ container: 'mx_LocationPicker_map', - style: config.map_style_url, + style: findMapStyleUrl(), center: [0, 0], zoom: 1, }); diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index a2fcfe09824..15ef333c21b 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -35,6 +35,7 @@ import LocationViewDialog from '../location/LocationViewDialog'; import TooltipTarget from '../elements/TooltipTarget'; import { Alignment } from '../elements/Tooltip'; import AccessibleButton from '../elements/AccessibleButton'; +import { getTileServerWellKnown } from '../../../utils/WellKnownUtils'; interface IState { error: Error; @@ -206,6 +207,19 @@ function ZoomButtons(props: IZoomButtonsProps): React.ReactElement; } +/** + * Look up what map tile server style URL was provided in the homeserver's + * .well-known location, or, failing that, in our local config, or, failing + * that, defaults to the same tile server listed by matrix.org. + */ +export function findMapStyleUrl(): string { + return ( + getTileServerWellKnown().map_style_url ?? + SdkConfig.get().map_style_url ?? + "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx" + ); +} + export function createMap( coords: GeolocationCoordinates, interactive: boolean, @@ -213,7 +227,7 @@ export function createMap( markerId: string, onError: (error: Error) => void, ): maplibregl.Map { - const styleUrl = SdkConfig.get().map_style_url; + const styleUrl = findMapStyleUrl(); const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude); const map = new maplibregl.Map({ diff --git a/src/utils/WellKnownUtils.ts b/src/utils/WellKnownUtils.ts index 59307d642ed..87776cb8c85 100644 --- a/src/utils/WellKnownUtils.ts +++ b/src/utils/WellKnownUtils.ts @@ -14,11 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { UnstableValue } from 'matrix-js-sdk/src/NamespacedValue'; + import { MatrixClientPeg } from '../MatrixClientPeg'; const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour"; const E2EE_WK_KEY = "io.element.e2ee"; const E2EE_WK_KEY_DEPRECATED = "im.vector.riot.e2ee"; +const TILE_SERVER_WK_KEY = new UnstableValue( + "m.tile_server", "org.matrix.msc3488.tile_server"); /* eslint-disable camelcase */ export interface ICallBehaviourWellKnown { @@ -30,6 +34,10 @@ export interface IE2EEWellKnown { secure_backup_required?: boolean; secure_backup_setup_methods?: SecureBackupSetupMethod[]; } + +export interface ITileServerWellKnown { + map_style_url?: string; +} /* eslint-enable camelcase */ export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown { @@ -48,6 +56,14 @@ export function getE2EEWellKnown(): IE2EEWellKnown { return null; } +export function getTileServerWellKnown(): ITileServerWellKnown { + const clientWellKnown = MatrixClientPeg.get().getClientWellKnown(); + return ( + clientWellKnown?.[TILE_SERVER_WK_KEY.name] ?? + clientWellKnown?.[TILE_SERVER_WK_KEY.altName] + ); +} + export function isSecureBackupRequired(): boolean { const wellKnown = getE2EEWellKnown(); return wellKnown && wellKnown["secure_backup_required"] === true; From 8201f170400619c7c13bb7f2946e6675434aaa20 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 25 Jan 2022 13:46:04 +0000 Subject: [PATCH 2/6] Fix crash if well-known entry is missing on homeserver --- src/components/views/messages/MLocationBody.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index 15ef333c21b..97e29d39485 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -214,7 +214,7 @@ function ZoomButtons(props: IZoomButtonsProps): React.ReactElement Date: Tue, 25 Jan 2022 14:41:03 +0000 Subject: [PATCH 3/6] Remove map tile server fallback and better handle missing config --- .../views/location/LocationPicker.tsx | 18 ++--- .../views/messages/MLocationBody.tsx | 77 +++++++++++-------- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 84e76f1793e..4cdc8fd9876 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -69,14 +69,14 @@ class LocationPicker extends React.Component { }; componentDidMount() { - this.map = new maplibregl.Map({ - container: 'mx_LocationPicker_map', - style: findMapStyleUrl(), - center: [0, 0], - zoom: 1, - }); - try { + this.map = new maplibregl.Map({ + container: 'mx_LocationPicker_map', + style: findMapStyleUrl(), + center: [0, 0], + zoom: 1, + }); + // Add geolocate control to the map. this.geolocate = new maplibregl.GeolocateControl({ positionOptions: { @@ -123,8 +123,8 @@ class LocationPicker extends React.Component { this.geolocate.on('geolocate', this.onGeolocate); } catch (e) { - logger.error("Failed to render map", e.error); - this.setState({ error: e.error }); + logger.error("Failed to render map", e); + this.setState({ error: e }); } } diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index 97e29d39485..0b857ca5d23 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -213,11 +213,19 @@ function ZoomButtons(props: IZoomButtonsProps): React.ReactElement void, ): maplibregl.Map { - const styleUrl = findMapStyleUrl(); - const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude); - - const map = new maplibregl.Map({ - container: bodyId, - style: styleUrl, - center: coordinates, - zoom: 15, - interactive, - }); - - new maplibregl.Marker({ - element: document.getElementById(markerId), - anchor: 'bottom', - offset: [0, -1], - }) - .setLngLat(coordinates) - .addTo(map); - - map.on('error', (e) => { - logger.error( - "Failed to load map: check map_style_url in config.json has a " - + "valid URL and API key", - e.error, - ); - onError(e.error); - }); - - return map; + try { + const styleUrl = findMapStyleUrl(); + const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude); + + const map = new maplibregl.Map({ + container: bodyId, + style: styleUrl, + center: coordinates, + zoom: 15, + interactive, + }); + + new maplibregl.Marker({ + element: document.getElementById(markerId), + anchor: 'bottom', + offset: [0, -1], + }) + .setLngLat(coordinates) + .addTo(map); + + map.on('error', (e) => { + logger.error( + "Failed to load map: check map_style_url in config.json has a " + + "valid URL and API key", + e.error, + ); + onError(e.error); + }); + + return map; + } catch (e) { + logger.error("Failed to render map", e); + onError(e); + } } /** From 6bb8d82febb9db86770426d482d5c7ec567feb2d Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 26 Jan 2022 16:58:56 +0000 Subject: [PATCH 4/6] Update map style when .well-known config changes --- .../views/location/LocationPicker.tsx | 23 ++++++++++++--- .../views/location/LocationViewDialog.tsx | 16 +++++++++++ .../views/messages/MLocationBody.tsx | 28 +++++++++++++++++-- src/utils/WellKnownUtils.ts | 10 +++++-- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 4cdc8fd9876..789c4b2b969 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -18,6 +18,7 @@ import React, { SyntheticEvent } from 'react'; import maplibregl from 'maplibre-gl'; import { logger } from "matrix-js-sdk/src/logger"; import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; +import { IClientWellKnown } from 'matrix-js-sdk/src/client'; import DialogButtons from "../elements/DialogButtons"; import { _t } from '../../../languageHandler'; @@ -27,6 +28,7 @@ import MatrixClientContext from '../../../contexts/MatrixClientContext'; import Modal from '../../../Modal'; import ErrorDialog from '../dialogs/ErrorDialog'; import { findMapStyleUrl } from '../messages/MLocationBody'; +import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; interface IProps { sender: RoomMember; @@ -51,9 +53,9 @@ interface IState { class LocationPicker extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; - private map: maplibregl.Map; - private geolocate: maplibregl.GeolocateControl; - private marker: maplibregl.Marker; + private map?: maplibregl.Map; + private geolocate?: maplibregl.GeolocateControl; + private marker?: maplibregl.Marker; constructor(props: IProps) { super(props); @@ -62,6 +64,9 @@ class LocationPicker extends React.Component { position: undefined, error: undefined, }; + this.map = null; + this.geolocate = null; + this.marker = null; } private getMarkerId = () => { @@ -69,6 +74,8 @@ class LocationPicker extends React.Component { }; componentDidMount() { + this.context.on("WellKnown.client", this.updateStyleUrl); + try { this.map = new maplibregl.Map({ container: 'mx_LocationPicker_map', @@ -130,11 +137,19 @@ class LocationPicker extends React.Component { componentWillUnmount() { this.geolocate?.off('geolocate', this.onGeolocate); + this.context.off("WellKnown.client", this.updateStyleUrl); } + private updateStyleUrl = (clientWellKnown: IClientWellKnown) => { + const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"]; + if (style) { + this.map?.setStyle(style); + } + }; + private onGeolocate = (position: GeolocationPosition) => { this.setState({ position }); - this.marker.setLngLat( + this.marker?.setLngLat( new maplibregl.LngLat( position.coords.longitude, position.coords.latitude, diff --git a/src/components/views/location/LocationViewDialog.tsx b/src/components/views/location/LocationViewDialog.tsx index 438f6a5f16a..e09e73aded2 100644 --- a/src/components/views/location/LocationViewDialog.tsx +++ b/src/components/views/location/LocationViewDialog.tsx @@ -16,13 +16,16 @@ limitations under the License. import React from 'react'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { IClientWellKnown, MatrixClient } from 'matrix-js-sdk/src/client'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "../dialogs/BaseDialog"; import { IDialogProps } from "../dialogs/IDialogProps"; import { createMap, LocationBodyContent, locationEventGeoUri, parseGeoUri } from '../messages/MLocationBody'; +import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; interface IProps extends IDialogProps { + matrixClient: MatrixClient; mxEvent: MatrixEvent; } @@ -50,6 +53,8 @@ export default class LocationViewDialog extends React.Component return; } + this.props.matrixClient.on("WellKnown.client", this.updateStyleUrl); + this.map = createMap( this.coords, true, @@ -59,6 +64,17 @@ export default class LocationViewDialog extends React.Component ); } + componentWillUnmount() { + this.props.matrixClient.off("WellKnown.client", this.updateStyleUrl); + } + + private updateStyleUrl = (clientWellKnown: IClientWellKnown) => { + const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"]; + if (style) { + this.map?.setStyle(style); + } + }; + private getBodyId = () => { return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`; }; diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index 0b857ca5d23..f11380686ac 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -24,6 +24,7 @@ import { ILocationContent, LOCATION_EVENT_TYPE, } from 'matrix-js-sdk/src/@types/location'; +import { IClientWellKnown } from 'matrix-js-sdk/src/client'; import SdkConfig from '../../../SdkConfig'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -35,7 +36,8 @@ import LocationViewDialog from '../location/LocationViewDialog'; import TooltipTarget from '../elements/TooltipTarget'; import { Alignment } from '../elements/Tooltip'; import AccessibleButton from '../elements/AccessibleButton'; -import { getTileServerWellKnown } from '../../../utils/WellKnownUtils'; +import { getTileServerWellKnown, tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; +import MatrixClientContext from '../../../contexts/MatrixClientContext'; interface IState { error: Error; @@ -43,9 +45,12 @@ interface IState { @replaceableComponent("views.messages.MLocationBody") export default class MLocationBody extends React.Component { + public static contextType = MatrixClientContext; + public context!: React.ContextType; private coords: GeolocationCoordinates; private bodyId: string; private markerId: string; + private map?: maplibregl.Map; constructor(props: IBodyProps) { super(props); @@ -55,6 +60,7 @@ export default class MLocationBody extends React.Component { this.bodyId = `mx_MLocationBody_${idSuffix}`; this.markerId = `mx_MLocationBody_marker_${idSuffix}`; this.coords = parseGeoUri(locationEventGeoUri(this.props.mxEvent)); + this.map = null; this.state = { error: undefined, @@ -66,7 +72,9 @@ export default class MLocationBody extends React.Component { return; } - createMap( + this.context.on("WellKnown.client", this.updateStyleUrl); + + this.map = createMap( this.coords, false, this.bodyId, @@ -75,6 +83,17 @@ export default class MLocationBody extends React.Component { ); } + componentWillUnmount() { + this.context.off("WellKnown.client", this.updateStyleUrl); + } + + private updateStyleUrl = (clientWellKnown: IClientWellKnown) => { + const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"]; + if (style) { + this.map?.setStyle(style); + } + }; + private onClick = ( event: React.MouseEvent, ) => { @@ -88,7 +107,10 @@ export default class MLocationBody extends React.Component { 'Location View', '', LocationViewDialog, - { mxEvent: this.props.mxEvent }, + { + matrixClient: this.context, + mxEvent: this.props.mxEvent, + }, "mx_LocationViewDialog_wrapper", false, // isPriority true, // isStatic diff --git a/src/utils/WellKnownUtils.ts b/src/utils/WellKnownUtils.ts index 87776cb8c85..cb3e92a7bde 100644 --- a/src/utils/WellKnownUtils.ts +++ b/src/utils/WellKnownUtils.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { IClientWellKnown } from 'matrix-js-sdk/src/client'; import { UnstableValue } from 'matrix-js-sdk/src/NamespacedValue'; import { MatrixClientPeg } from '../MatrixClientPeg'; @@ -56,8 +57,13 @@ export function getE2EEWellKnown(): IE2EEWellKnown { return null; } -export function getTileServerWellKnown(): ITileServerWellKnown { - const clientWellKnown = MatrixClientPeg.get().getClientWellKnown(); +export function getTileServerWellKnown(): ITileServerWellKnown | undefined { + return tileServerFromWellKnown(MatrixClientPeg.get().getClientWellKnown()); +} + +export function tileServerFromWellKnown( + clientWellKnown?: IClientWellKnown | undefined, +): ITileServerWellKnown { return ( clientWellKnown?.[TILE_SERVER_WK_KEY.name] ?? clientWellKnown?.[TILE_SERVER_WK_KEY.altName] From e49c8a2ed4313ee2c1f400ecb6990cc05fc9fb31 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 27 Jan 2022 09:35:58 +0000 Subject: [PATCH 5/6] Respond to review comments --- src/components/views/location/LocationPicker.tsx | 9 +++------ src/components/views/messages/MLocationBody.tsx | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 789c4b2b969..104665e2503 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -53,9 +53,9 @@ interface IState { class LocationPicker extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; - private map?: maplibregl.Map; - private geolocate?: maplibregl.GeolocateControl; - private marker?: maplibregl.Marker; + private map?: maplibregl.Map= null; + private geolocate?: maplibregl.GeolocateControl = null; + private marker?: maplibregl.Marker = null; constructor(props: IProps) { super(props); @@ -64,9 +64,6 @@ class LocationPicker extends React.Component { position: undefined, error: undefined, }; - this.map = null; - this.geolocate = null; - this.marker = null; } private getMarkerId = () => { diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index f11380686ac..9d4c2d13c3e 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -50,7 +50,7 @@ export default class MLocationBody extends React.Component { private coords: GeolocationCoordinates; private bodyId: string; private markerId: string; - private map?: maplibregl.Map; + private map?: maplibregl.Map = null; constructor(props: IBodyProps) { super(props); @@ -60,7 +60,6 @@ export default class MLocationBody extends React.Component { this.bodyId = `mx_MLocationBody_${idSuffix}`; this.markerId = `mx_MLocationBody_marker_${idSuffix}`; this.coords = parseGeoUri(locationEventGeoUri(this.props.mxEvent)); - this.map = null; this.state = { error: undefined, From 25710d639c98c72d2b3e9c305bfd6bc23e9a709f Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 27 Jan 2022 09:43:54 +0000 Subject: [PATCH 6/6] Update src/components/views/location/LocationPicker.tsx Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/location/LocationPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 104665e2503..33f91ff7313 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -53,7 +53,7 @@ interface IState { class LocationPicker extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; - private map?: maplibregl.Map= null; + private map?: maplibregl.Map = null; private geolocate?: maplibregl.GeolocateControl = null; private marker?: maplibregl.Marker = null;