mirror of https://github.com/vector-im/riot-web
Look up tile server info in homeserver's .well-known area (#7623)
parent
20819df60b
commit
ae490841c6
|
@ -18,8 +18,8 @@ import React, { SyntheticEvent } from 'react';
|
||||||
import maplibregl from 'maplibre-gl';
|
import maplibregl from 'maplibre-gl';
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||||
|
import { IClientWellKnown } from 'matrix-js-sdk/src/client';
|
||||||
|
|
||||||
import SdkConfig from '../../../SdkConfig';
|
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -27,6 +27,8 @@ import MemberAvatar from '../avatars/MemberAvatar';
|
||||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import ErrorDialog from '../dialogs/ErrorDialog';
|
import ErrorDialog from '../dialogs/ErrorDialog';
|
||||||
|
import { findMapStyleUrl } from '../messages/MLocationBody';
|
||||||
|
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
sender: RoomMember;
|
sender: RoomMember;
|
||||||
|
@ -51,9 +53,9 @@ interface IState {
|
||||||
class LocationPicker extends React.Component<IProps, IState> {
|
class LocationPicker extends React.Component<IProps, IState> {
|
||||||
public static contextType = MatrixClientContext;
|
public static contextType = MatrixClientContext;
|
||||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||||
private map: maplibregl.Map;
|
private map?: maplibregl.Map = null;
|
||||||
private geolocate: maplibregl.GeolocateControl;
|
private geolocate?: maplibregl.GeolocateControl = null;
|
||||||
private marker: maplibregl.Marker;
|
private marker?: maplibregl.Marker = null;
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -69,15 +71,16 @@ class LocationPicker extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const config = SdkConfig.get();
|
this.context.on("WellKnown.client", this.updateStyleUrl);
|
||||||
this.map = new maplibregl.Map({
|
|
||||||
container: 'mx_LocationPicker_map',
|
|
||||||
style: config.map_style_url,
|
|
||||||
center: [0, 0],
|
|
||||||
zoom: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.map = new maplibregl.Map({
|
||||||
|
container: 'mx_LocationPicker_map',
|
||||||
|
style: findMapStyleUrl(),
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1,
|
||||||
|
});
|
||||||
|
|
||||||
// Add geolocate control to the map.
|
// Add geolocate control to the map.
|
||||||
this.geolocate = new maplibregl.GeolocateControl({
|
this.geolocate = new maplibregl.GeolocateControl({
|
||||||
positionOptions: {
|
positionOptions: {
|
||||||
|
@ -124,18 +127,26 @@ class LocationPicker extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
this.geolocate.on('geolocate', this.onGeolocate);
|
this.geolocate.on('geolocate', this.onGeolocate);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to render map", e.error);
|
logger.error("Failed to render map", e);
|
||||||
this.setState({ error: e.error });
|
this.setState({ error: e });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.geolocate?.off('geolocate', this.onGeolocate);
|
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) => {
|
private onGeolocate = (position: GeolocationPosition) => {
|
||||||
this.setState({ position });
|
this.setState({ position });
|
||||||
this.marker.setLngLat(
|
this.marker?.setLngLat(
|
||||||
new maplibregl.LngLat(
|
new maplibregl.LngLat(
|
||||||
position.coords.longitude,
|
position.coords.longitude,
|
||||||
position.coords.latitude,
|
position.coords.latitude,
|
||||||
|
|
|
@ -16,13 +16,16 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||||
|
import { IClientWellKnown, MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import BaseDialog from "../dialogs/BaseDialog";
|
import BaseDialog from "../dialogs/BaseDialog";
|
||||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||||
import { createMap, LocationBodyContent, locationEventGeoUri, parseGeoUri } from '../messages/MLocationBody';
|
import { createMap, LocationBodyContent, locationEventGeoUri, parseGeoUri } from '../messages/MLocationBody';
|
||||||
|
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
|
matrixClient: MatrixClient;
|
||||||
mxEvent: MatrixEvent;
|
mxEvent: MatrixEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +53,8 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.props.matrixClient.on("WellKnown.client", this.updateStyleUrl);
|
||||||
|
|
||||||
this.map = createMap(
|
this.map = createMap(
|
||||||
this.coords,
|
this.coords,
|
||||||
true,
|
true,
|
||||||
|
@ -59,6 +64,17 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = () => {
|
private getBodyId = () => {
|
||||||
return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`;
|
return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
ILocationContent,
|
ILocationContent,
|
||||||
LOCATION_EVENT_TYPE,
|
LOCATION_EVENT_TYPE,
|
||||||
} from 'matrix-js-sdk/src/@types/location';
|
} from 'matrix-js-sdk/src/@types/location';
|
||||||
|
import { IClientWellKnown } from 'matrix-js-sdk/src/client';
|
||||||
|
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -35,6 +36,8 @@ import LocationViewDialog from '../location/LocationViewDialog';
|
||||||
import TooltipTarget from '../elements/TooltipTarget';
|
import TooltipTarget from '../elements/TooltipTarget';
|
||||||
import { Alignment } from '../elements/Tooltip';
|
import { Alignment } from '../elements/Tooltip';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import { getTileServerWellKnown, tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
|
||||||
|
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
error: Error;
|
error: Error;
|
||||||
|
@ -42,9 +45,12 @@ interface IState {
|
||||||
|
|
||||||
@replaceableComponent("views.messages.MLocationBody")
|
@replaceableComponent("views.messages.MLocationBody")
|
||||||
export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||||
|
public static contextType = MatrixClientContext;
|
||||||
|
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||||
private coords: GeolocationCoordinates;
|
private coords: GeolocationCoordinates;
|
||||||
private bodyId: string;
|
private bodyId: string;
|
||||||
private markerId: string;
|
private markerId: string;
|
||||||
|
private map?: maplibregl.Map = null;
|
||||||
|
|
||||||
constructor(props: IBodyProps) {
|
constructor(props: IBodyProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -65,7 +71,9 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createMap(
|
this.context.on("WellKnown.client", this.updateStyleUrl);
|
||||||
|
|
||||||
|
this.map = createMap(
|
||||||
this.coords,
|
this.coords,
|
||||||
false,
|
false,
|
||||||
this.bodyId,
|
this.bodyId,
|
||||||
|
@ -74,6 +82,17 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = (
|
private onClick = (
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -87,7 +106,10 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||||
'Location View',
|
'Location View',
|
||||||
'',
|
'',
|
||||||
LocationViewDialog,
|
LocationViewDialog,
|
||||||
{ mxEvent: this.props.mxEvent },
|
{
|
||||||
|
matrixClient: this.context,
|
||||||
|
mxEvent: this.props.mxEvent,
|
||||||
|
},
|
||||||
"mx_LocationViewDialog_wrapper",
|
"mx_LocationViewDialog_wrapper",
|
||||||
false, // isPriority
|
false, // isPriority
|
||||||
true, // isStatic
|
true, // isStatic
|
||||||
|
@ -206,6 +228,27 @@ function ZoomButtons(props: IZoomButtonsProps): React.ReactElement<HTMLDivElemen
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
const mapStyleUrl = (
|
||||||
|
getTileServerWellKnown()?.map_style_url ??
|
||||||
|
SdkConfig.get().map_style_url
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mapStyleUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"'map_style_url' missing from homeserver .well-known area, and " +
|
||||||
|
"missing from from config.json.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapStyleUrl;
|
||||||
|
}
|
||||||
|
|
||||||
export function createMap(
|
export function createMap(
|
||||||
coords: GeolocationCoordinates,
|
coords: GeolocationCoordinates,
|
||||||
interactive: boolean,
|
interactive: boolean,
|
||||||
|
@ -213,35 +256,40 @@ export function createMap(
|
||||||
markerId: string,
|
markerId: string,
|
||||||
onError: (error: Error) => void,
|
onError: (error: Error) => void,
|
||||||
): maplibregl.Map {
|
): maplibregl.Map {
|
||||||
const styleUrl = SdkConfig.get().map_style_url;
|
try {
|
||||||
const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude);
|
const styleUrl = findMapStyleUrl();
|
||||||
|
const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude);
|
||||||
|
|
||||||
const map = new maplibregl.Map({
|
const map = new maplibregl.Map({
|
||||||
container: bodyId,
|
container: bodyId,
|
||||||
style: styleUrl,
|
style: styleUrl,
|
||||||
center: coordinates,
|
center: coordinates,
|
||||||
zoom: 15,
|
zoom: 15,
|
||||||
interactive,
|
interactive,
|
||||||
});
|
});
|
||||||
|
|
||||||
new maplibregl.Marker({
|
new maplibregl.Marker({
|
||||||
element: document.getElementById(markerId),
|
element: document.getElementById(markerId),
|
||||||
anchor: 'bottom',
|
anchor: 'bottom',
|
||||||
offset: [0, -1],
|
offset: [0, -1],
|
||||||
})
|
})
|
||||||
.setLngLat(coordinates)
|
.setLngLat(coordinates)
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
|
|
||||||
map.on('error', (e) => {
|
map.on('error', (e) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to load map: check map_style_url in config.json has a "
|
"Failed to load map: check map_style_url in config.json has a "
|
||||||
+ "valid URL and API key",
|
+ "valid URL and API key",
|
||||||
e.error,
|
e.error,
|
||||||
);
|
);
|
||||||
onError(e.error);
|
onError(e.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Failed to render map", e);
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,11 +14,16 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IClientWellKnown } from 'matrix-js-sdk/src/client';
|
||||||
|
import { UnstableValue } from 'matrix-js-sdk/src/NamespacedValue';
|
||||||
|
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
|
|
||||||
const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour";
|
const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour";
|
||||||
const E2EE_WK_KEY = "io.element.e2ee";
|
const E2EE_WK_KEY = "io.element.e2ee";
|
||||||
const E2EE_WK_KEY_DEPRECATED = "im.vector.riot.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 */
|
/* eslint-disable camelcase */
|
||||||
export interface ICallBehaviourWellKnown {
|
export interface ICallBehaviourWellKnown {
|
||||||
|
@ -30,6 +35,10 @@ export interface IE2EEWellKnown {
|
||||||
secure_backup_required?: boolean;
|
secure_backup_required?: boolean;
|
||||||
secure_backup_setup_methods?: SecureBackupSetupMethod[];
|
secure_backup_setup_methods?: SecureBackupSetupMethod[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITileServerWellKnown {
|
||||||
|
map_style_url?: string;
|
||||||
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown {
|
export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown {
|
||||||
|
@ -48,6 +57,19 @@ export function getE2EEWellKnown(): IE2EEWellKnown {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function isSecureBackupRequired(): boolean {
|
export function isSecureBackupRequired(): boolean {
|
||||||
const wellKnown = getE2EEWellKnown();
|
const wellKnown = getE2EEWellKnown();
|
||||||
return wellKnown && wellKnown["secure_backup_required"] === true;
|
return wellKnown && wellKnown["secure_backup_required"] === true;
|
||||||
|
|
Loading…
Reference in New Issue