Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
|
@ -14,49 +14,49 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import StyledLiveBeaconIcon from '../beacon/StyledLiveBeaconIcon';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import LabelledToggleSwitch from '../elements/LabelledToggleSwitch';
|
||||
import Heading from '../typography/Heading';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import StyledLiveBeaconIcon from "../beacon/StyledLiveBeaconIcon";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import Heading from "../typography/Heading";
|
||||
|
||||
interface Props {
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export const EnableLiveShare: React.FC<Props> = ({
|
||||
onSubmit,
|
||||
}) => {
|
||||
export const EnableLiveShare: React.FC<Props> = ({ onSubmit }) => {
|
||||
const [isEnabled, setEnabled] = useState(false);
|
||||
return (
|
||||
<div data-test-id='location-picker-enable-live-share' className='mx_EnableLiveShare'>
|
||||
<StyledLiveBeaconIcon className='mx_EnableLiveShare_icon' />
|
||||
<Heading className='mx_EnableLiveShare_heading' size='h3'>{ _t('Live location sharing') }</Heading>
|
||||
<p className='mx_EnableLiveShare_description'>
|
||||
{ _t(
|
||||
'Please note: this is a labs feature using a temporary implementation. ' +
|
||||
'This means you will not be able to delete your location history, ' +
|
||||
'and advanced users will be able to see your location history ' +
|
||||
'even after you stop sharing your live location with this room.',
|
||||
) }
|
||||
<div data-test-id="location-picker-enable-live-share" className="mx_EnableLiveShare">
|
||||
<StyledLiveBeaconIcon className="mx_EnableLiveShare_icon" />
|
||||
<Heading className="mx_EnableLiveShare_heading" size="h3">
|
||||
{_t("Live location sharing")}
|
||||
</Heading>
|
||||
<p className="mx_EnableLiveShare_description">
|
||||
{_t(
|
||||
"Please note: this is a labs feature using a temporary implementation. " +
|
||||
"This means you will not be able to delete your location history, " +
|
||||
"and advanced users will be able to see your location history " +
|
||||
"even after you stop sharing your live location with this room.",
|
||||
)}
|
||||
</p>
|
||||
<LabelledToggleSwitch
|
||||
data-test-id='enable-live-share-toggle'
|
||||
data-test-id="enable-live-share-toggle"
|
||||
value={isEnabled}
|
||||
onChange={setEnabled}
|
||||
label={_t('Enable live location sharing')}
|
||||
label={_t("Enable live location sharing")}
|
||||
/>
|
||||
<AccessibleButton
|
||||
data-test-id='enable-live-share-submit'
|
||||
className='mx_EnableLiveShare_button'
|
||||
element='button'
|
||||
kind='primary'
|
||||
data-test-id="enable-live-share-submit"
|
||||
className="mx_EnableLiveShare_button"
|
||||
element="button"
|
||||
kind="primary"
|
||||
onClick={onSubmit}
|
||||
disabled={!isEnabled}
|
||||
>
|
||||
{ _t('OK') }
|
||||
{_t("OK")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { formatDuration } from '../../../DateUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Dropdown from '../elements/Dropdown';
|
||||
import { formatDuration } from "../../../DateUtils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
|
||||
const DURATION_MS = {
|
||||
fifteenMins: 900000,
|
||||
|
@ -34,19 +34,23 @@ interface Props {
|
|||
}
|
||||
|
||||
const getLabel = (durationMs: number) => {
|
||||
return _t('Share for %(duration)s', { duration: formatDuration(durationMs) });
|
||||
return _t("Share for %(duration)s", { duration: formatDuration(durationMs) });
|
||||
};
|
||||
|
||||
const LiveDurationDropdown: React.FC<Props> = ({ timeout, onChange }) => {
|
||||
const options = Object.values(DURATION_MS).map((duration) =>
|
||||
({ key: duration.toString(), duration, label: getLabel(duration) }),
|
||||
);
|
||||
const options = Object.values(DURATION_MS).map((duration) => ({
|
||||
key: duration.toString(),
|
||||
duration,
|
||||
label: getLabel(duration),
|
||||
}));
|
||||
|
||||
// timeout is not one of our default values
|
||||
// eg it was set by another client
|
||||
if (!Object.values(DURATION_MS).includes(timeout)) {
|
||||
options.push({
|
||||
key: timeout.toString(), duration: timeout, label: getLabel(timeout),
|
||||
key: timeout.toString(),
|
||||
duration: timeout,
|
||||
label: getLabel(timeout),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -55,18 +59,22 @@ const LiveDurationDropdown: React.FC<Props> = ({ timeout, onChange }) => {
|
|||
onChange(+key);
|
||||
};
|
||||
|
||||
return <Dropdown
|
||||
id='live-duration'
|
||||
data-test-id='live-duration-dropdown'
|
||||
label={getLabel(timeout)}
|
||||
value={timeout.toString()}
|
||||
onOptionChange={onOptionChange}
|
||||
className='mx_LiveDurationDropdown'
|
||||
>
|
||||
{ options.map(({ key, label }) =>
|
||||
<div data-test-id={`live-duration-option-${key}`} key={key}>{ label }</div>,
|
||||
) }
|
||||
</Dropdown>;
|
||||
return (
|
||||
<Dropdown
|
||||
id="live-duration"
|
||||
data-test-id="live-duration-dropdown"
|
||||
label={getLabel(timeout)}
|
||||
value={timeout.toString()}
|
||||
onOptionChange={onOptionChange}
|
||||
className="mx_LiveDurationDropdown"
|
||||
>
|
||||
{options.map(({ key, label }) => (
|
||||
<div data-test-id={`live-duration-option-${key}`} key={key}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveDurationDropdown;
|
||||
|
|
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement, SyntheticEvent, useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||
import { IEventRelation } from 'matrix-js-sdk/src/models/event';
|
||||
import React, { ReactElement, SyntheticEvent, useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { IEventRelation } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { CollapsibleButton } from '../rooms/CollapsibleButton';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { CollapsibleButton } from "../rooms/CollapsibleButton";
|
||||
import { aboveLeftOf, useContextMenu, AboveLeftOf } from "../../structures/ContextMenu";
|
||||
import { OverflowMenuContext } from "../rooms/MessageComposerButtons";
|
||||
import LocationShareMenu from './LocationShareMenu';
|
||||
import LocationShareMenu from "./LocationShareMenu";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -43,37 +43,37 @@ export const LocationButton: React.FC<IProps> = ({ roomId, sender, menuPosition,
|
|||
|
||||
let contextMenu: ReactElement;
|
||||
if (menuDisplayed) {
|
||||
const position = menuPosition ?? aboveLeftOf(
|
||||
button.current.getBoundingClientRect());
|
||||
const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
|
||||
|
||||
contextMenu = <LocationShareMenu
|
||||
menuPosition={position}
|
||||
onFinished={_onFinished}
|
||||
sender={sender}
|
||||
roomId={roomId}
|
||||
openMenu={openMenu}
|
||||
relation={relation}
|
||||
/>;
|
||||
contextMenu = (
|
||||
<LocationShareMenu
|
||||
menuPosition={position}
|
||||
onFinished={_onFinished}
|
||||
sender={sender}
|
||||
roomId={roomId}
|
||||
openMenu={openMenu}
|
||||
relation={relation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const className = classNames(
|
||||
"mx_MessageComposer_button",
|
||||
{
|
||||
"mx_MessageComposer_button_highlight": menuDisplayed,
|
||||
},
|
||||
const className = classNames("mx_MessageComposer_button", {
|
||||
mx_MessageComposer_button_highlight: menuDisplayed,
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CollapsibleButton
|
||||
className={className}
|
||||
iconClassName="mx_MessageComposer_location"
|
||||
onClick={openMenu}
|
||||
title={_t("Location")}
|
||||
inputRef={button}
|
||||
/>
|
||||
|
||||
{contextMenu}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return <React.Fragment>
|
||||
<CollapsibleButton
|
||||
className={className}
|
||||
iconClassName="mx_MessageComposer_location"
|
||||
onClick={openMenu}
|
||||
title={_t("Location")}
|
||||
inputRef={button}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
export default LocationButton;
|
||||
|
|
|
@ -14,25 +14,25 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import maplibregl, { MapMouseEvent } from 'maplibre-gl';
|
||||
import React, { SyntheticEvent } from "react";
|
||||
import maplibregl, { MapMouseEvent } from "maplibre-gl";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
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 ErrorDialog from '../dialogs/ErrorDialog';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { MapError } from './MapError';
|
||||
import LiveDurationDropdown, { DEFAULT_DURATION_MS } from './LiveDurationDropdown';
|
||||
import { LocationShareType, ShareLocationFn } from './shareLocation';
|
||||
import Marker from './Marker';
|
||||
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 ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { MapError } from "./MapError";
|
||||
import LiveDurationDropdown, { DEFAULT_DURATION_MS } from "./LiveDurationDropdown";
|
||||
import { LocationShareType, ShareLocationFn } from "./shareLocation";
|
||||
import Marker from "./Marker";
|
||||
|
||||
export interface ILocationPickerProps {
|
||||
sender: RoomMember;
|
||||
|
@ -76,7 +76,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
|||
|
||||
try {
|
||||
this.map = new maplibregl.Map({
|
||||
container: 'mx_LocationPicker_map',
|
||||
container: "mx_LocationPicker_map",
|
||||
style: findMapStyleUrl(),
|
||||
center: [0, 0],
|
||||
zoom: 1,
|
||||
|
@ -92,54 +92,56 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
|||
|
||||
this.map.addControl(this.geolocate);
|
||||
|
||||
this.map.on('error', (e) => {
|
||||
this.map.on("error", (e) => {
|
||||
logger.error(
|
||||
"Failed to load map: check map_style_url in config.json "
|
||||
+ "has a valid URL and API key",
|
||||
"Failed to load map: check map_style_url in config.json " + "has a valid URL and API key",
|
||||
e.error,
|
||||
);
|
||||
this.setState({ error: LocationShareError.MapStyleUrlNotReachable });
|
||||
});
|
||||
|
||||
this.map.on('load', () => {
|
||||
this.map.on("load", () => {
|
||||
this.geolocate.trigger();
|
||||
});
|
||||
|
||||
this.geolocate.on('error', this.onGeolocateError);
|
||||
this.geolocate.on("error", this.onGeolocateError);
|
||||
|
||||
if (isSharingOwnLocation(this.props.shareType)) {
|
||||
this.geolocate.on('geolocate', this.onGeolocate);
|
||||
this.geolocate.on("geolocate", this.onGeolocate);
|
||||
}
|
||||
|
||||
if (this.props.shareType === LocationShareType.Pin) {
|
||||
const navigationControl = new maplibregl.NavigationControl({
|
||||
showCompass: false, showZoom: true,
|
||||
showCompass: false,
|
||||
showZoom: true,
|
||||
});
|
||||
this.map.addControl(navigationControl, 'bottom-right');
|
||||
this.map.on('click', this.onClick);
|
||||
this.map.addControl(navigationControl, "bottom-right");
|
||||
this.map.on("click", this.onClick);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Failed to render map", e);
|
||||
const errorType = e?.message === LocationShareError.MapStyleUrlNotConfigured ?
|
||||
LocationShareError.MapStyleUrlNotConfigured :
|
||||
LocationShareError.Default;
|
||||
const errorType =
|
||||
e?.message === LocationShareError.MapStyleUrlNotConfigured
|
||||
? LocationShareError.MapStyleUrlNotConfigured
|
||||
: LocationShareError.Default;
|
||||
this.setState({ error: errorType });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.geolocate?.off('error', this.onGeolocateError);
|
||||
this.geolocate?.off('geolocate', this.onGeolocate);
|
||||
this.map?.off('click', this.onClick);
|
||||
this.geolocate?.off("error", this.onGeolocateError);
|
||||
this.geolocate?.off("geolocate", this.onGeolocate);
|
||||
this.map?.off("click", this.onClick);
|
||||
this.context.off(ClientEvent.ClientWellKnown, this.updateStyleUrl);
|
||||
}
|
||||
|
||||
private addMarkerToMap = () => {
|
||||
this.marker = new maplibregl.Marker({
|
||||
element: document.getElementById(this.getMarkerId()),
|
||||
anchor: 'bottom',
|
||||
anchor: "bottom",
|
||||
offset: [0, -1],
|
||||
}).setLngLat(new maplibregl.LngLat(0, 0))
|
||||
})
|
||||
.setLngLat(new maplibregl.LngLat(0, 0))
|
||||
.addTo(this.map);
|
||||
};
|
||||
|
||||
|
@ -155,12 +157,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
|||
this.addMarkerToMap();
|
||||
}
|
||||
this.setState({ position: genericPositionFromGeolocation(position) });
|
||||
this.marker?.setLngLat(
|
||||
new maplibregl.LngLat(
|
||||
position.coords.longitude,
|
||||
position.coords.latitude,
|
||||
),
|
||||
);
|
||||
this.marker?.setLngLat(new maplibregl.LngLat(position.coords.longitude, position.coords.latitude));
|
||||
};
|
||||
|
||||
private onClick = (event: MapMouseEvent) => {
|
||||
|
@ -202,65 +199,66 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
|||
const { timeout, position } = this.state;
|
||||
|
||||
this.props.onChoose(
|
||||
position ? { uri: getGeoUri(position), timestamp: position.timestamp, timeout } : {
|
||||
timeout,
|
||||
});
|
||||
position
|
||||
? { uri: getGeoUri(position), timestamp: position.timestamp, timeout }
|
||||
: {
|
||||
timeout,
|
||||
},
|
||||
);
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <div className="mx_LocationPicker mx_LocationPicker_hasError">
|
||||
<MapError
|
||||
error={this.state.error}
|
||||
onFinished={this.props.onFinished} />
|
||||
</div>;
|
||||
return (
|
||||
<div className="mx_LocationPicker mx_LocationPicker_hasError">
|
||||
<MapError error={this.state.error} onFinished={this.props.onFinished} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_LocationPicker">
|
||||
<div id="mx_LocationPicker_map" />
|
||||
|
||||
{ this.props.shareType === LocationShareType.Pin && <div className="mx_LocationPicker_pinText">
|
||||
<span>
|
||||
{ this.state.position ? _t("Click to move the pin") : _t("Click to drop a pin") }
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
{this.props.shareType === LocationShareType.Pin && (
|
||||
<div className="mx_LocationPicker_pinText">
|
||||
<span>{this.state.position ? _t("Click to move the pin") : _t("Click to drop a pin")}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="mx_LocationPicker_footer">
|
||||
<form onSubmit={this.onOk}>
|
||||
{ this.props.shareType === LocationShareType.Live &&
|
||||
<LiveDurationDropdown
|
||||
onChange={this.onTimeoutChange}
|
||||
timeout={this.state.timeout}
|
||||
/>
|
||||
}
|
||||
{this.props.shareType === LocationShareType.Live && (
|
||||
<LiveDurationDropdown onChange={this.onTimeoutChange} timeout={this.state.timeout} />
|
||||
)}
|
||||
<AccessibleButton
|
||||
data-test-id="location-picker-submit-button"
|
||||
type="submit"
|
||||
element='button'
|
||||
kind='primary'
|
||||
className='mx_LocationPicker_submitButton'
|
||||
element="button"
|
||||
kind="primary"
|
||||
className="mx_LocationPicker_submitButton"
|
||||
disabled={!this.state.position}
|
||||
onClick={this.onOk}>
|
||||
{ _t('Share location') }
|
||||
onClick={this.onOk}
|
||||
>
|
||||
{_t("Share location")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
<div id={this.getMarkerId()}>
|
||||
{ /*
|
||||
{/*
|
||||
maplibregl hijacks the div above to style the marker
|
||||
it must be in the dom when the map is initialised
|
||||
and keep a consistent class
|
||||
we want to hide the marker until it is set in the case of pin drop
|
||||
so hide the internal visible elements
|
||||
*/ }
|
||||
*/}
|
||||
|
||||
{ !!this.marker && <Marker
|
||||
roomMember={isSharingOwnLocation(this.props.shareType) ? this.props.sender : undefined}
|
||||
useMemberColor={this.props.shareType === LocationShareType.Live}
|
||||
/>
|
||||
}
|
||||
{!!this.marker && (
|
||||
<Marker
|
||||
roomMember={isSharingOwnLocation(this.props.shareType) ? this.props.sender : undefined}
|
||||
useMemberColor={this.props.shareType === LocationShareType.Live}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -272,18 +270,17 @@ 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.",
|
||||
);
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { SyntheticEvent, useContext, useState } from 'react';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import { IEventRelation } from 'matrix-js-sdk/src/models/event';
|
||||
import React, { SyntheticEvent, useContext, useState } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { IEventRelation } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import ContextMenu, { AboveLeftOf } from '../../structures/ContextMenu';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import ContextMenu, { AboveLeftOf } from "../../structures/ContextMenu";
|
||||
import LocationPicker, { ILocationPickerProps } from "./LocationPicker";
|
||||
import { shareLiveLocation, shareLocation, LocationShareType } from './shareLocation';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import ShareDialogButtons from './ShareDialogButtons';
|
||||
import ShareType from './ShareType';
|
||||
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
|
||||
import { EnableLiveShare } from './EnableLiveShare';
|
||||
import { useFeatureEnabled } from '../../../hooks/useSettings';
|
||||
import { SettingLevel } from '../../../settings/SettingLevel';
|
||||
import { shareLiveLocation, shareLocation, LocationShareType } from "./shareLocation";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import ShareDialogButtons from "./ShareDialogButtons";
|
||||
import ShareType from "./ShareType";
|
||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||
import { EnableLiveShare } from "./EnableLiveShare";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
|
||||
type Props = Omit<ILocationPickerProps, 'onChoose' | 'shareType'> & {
|
||||
type Props = Omit<ILocationPickerProps, "onChoose" | "shareType"> & {
|
||||
onFinished: (ev?: SyntheticEvent) => void;
|
||||
menuPosition: AboveLeftOf;
|
||||
openMenu: () => void;
|
||||
|
@ -39,9 +39,7 @@ type Props = Omit<ILocationPickerProps, 'onChoose' | 'shareType'> & {
|
|||
};
|
||||
|
||||
const getEnabledShareTypes = (relation): LocationShareType[] => {
|
||||
const enabledShareTypes = [
|
||||
LocationShareType.Own,
|
||||
];
|
||||
const enabledShareTypes = [LocationShareType.Own];
|
||||
|
||||
// live locations cannot have a relation
|
||||
// hide the option when composer has a relation
|
||||
|
@ -54,14 +52,7 @@ const getEnabledShareTypes = (relation): LocationShareType[] => {
|
|||
return enabledShareTypes;
|
||||
};
|
||||
|
||||
const LocationShareMenu: React.FC<Props> = ({
|
||||
menuPosition,
|
||||
onFinished,
|
||||
sender,
|
||||
roomId,
|
||||
openMenu,
|
||||
relation,
|
||||
}) => {
|
||||
const LocationShareMenu: React.FC<Props> = ({ menuPosition, onFinished, sender, roomId, openMenu, relation }) => {
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
const enabledShareTypes = getEnabledShareTypes(relation);
|
||||
const isLiveShareEnabled = useFeatureEnabled("feature_location_share_live");
|
||||
|
@ -74,9 +65,10 @@ const LocationShareMenu: React.FC<Props> = ({
|
|||
|
||||
const displayName = OwnProfileStore.instance.displayName;
|
||||
|
||||
const onLocationSubmit = shareType === LocationShareType.Live ?
|
||||
shareLiveLocation(matrixClient, roomId, displayName, openMenu) :
|
||||
shareLocation(matrixClient, roomId, shareType, relation, openMenu);
|
||||
const onLocationSubmit =
|
||||
shareType === LocationShareType.Live
|
||||
? shareLiveLocation(matrixClient, roomId, displayName, openMenu)
|
||||
: shareLocation(matrixClient, roomId, shareType, relation, openMenu);
|
||||
|
||||
const onLiveShareEnableSubmit = () => {
|
||||
SettingsStore.setValue("feature_location_share_live", undefined, SettingLevel.DEVICE, true);
|
||||
|
@ -84,31 +76,27 @@ const LocationShareMenu: React.FC<Props> = ({
|
|||
|
||||
const shouldAdvertiseLiveLabsFlag = shareType === LocationShareType.Live && !isLiveShareEnabled;
|
||||
|
||||
return <ContextMenu
|
||||
{...menuPosition}
|
||||
onFinished={onFinished}
|
||||
managed={false}
|
||||
>
|
||||
<div className="mx_LocationShareMenu">
|
||||
{ shouldAdvertiseLiveLabsFlag &&
|
||||
<EnableLiveShare
|
||||
onSubmit={onLiveShareEnableSubmit}
|
||||
return (
|
||||
<ContextMenu {...menuPosition} onFinished={onFinished} managed={false}>
|
||||
<div className="mx_LocationShareMenu">
|
||||
{shouldAdvertiseLiveLabsFlag && <EnableLiveShare onSubmit={onLiveShareEnableSubmit} />}
|
||||
{!shouldAdvertiseLiveLabsFlag && !!shareType && (
|
||||
<LocationPicker
|
||||
sender={sender}
|
||||
shareType={shareType}
|
||||
onChoose={onLocationSubmit}
|
||||
onFinished={onFinished}
|
||||
/>
|
||||
)}
|
||||
{!shareType && <ShareType setShareType={setShareType} enabledShareTypes={enabledShareTypes} />}
|
||||
<ShareDialogButtons
|
||||
displayBack={!!shareType && multipleShareTypesEnabled}
|
||||
onBack={() => setShareType(undefined)}
|
||||
onCancel={onFinished}
|
||||
/>
|
||||
}
|
||||
{ !shouldAdvertiseLiveLabsFlag && !!shareType &&
|
||||
<LocationPicker
|
||||
sender={sender}
|
||||
shareType={shareType}
|
||||
onChoose={onLocationSubmit}
|
||||
onFinished={onFinished}
|
||||
/>
|
||||
}
|
||||
{ !shareType &&
|
||||
<ShareType setShareType={setShareType} enabledShareTypes={enabledShareTypes} />
|
||||
}
|
||||
<ShareDialogButtons displayBack={!!shareType && multipleShareTypesEnabled} onBack={() => setShareType(undefined)} onCancel={onFinished} />
|
||||
</div>
|
||||
</ContextMenu>;
|
||||
</div>
|
||||
</ContextMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocationShareMenu;
|
||||
|
|
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||
import { locationEventGeoUri, isSelfLocation } from '../../../utils/location';
|
||||
import Map from './Map';
|
||||
import SmartMarker from './SmartMarker';
|
||||
import ZoomButtons from './ZoomButtons';
|
||||
import { locationEventGeoUri, isSelfLocation } from "../../../utils/location";
|
||||
import Map from "./Map";
|
||||
import SmartMarker from "./SmartMarker";
|
||||
import ZoomButtons from "./ZoomButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
matrixClient: MatrixClient;
|
||||
|
@ -61,11 +61,7 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
|
|||
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
||||
const geoUri = locationEventGeoUri(mxEvent);
|
||||
return (
|
||||
<BaseDialog
|
||||
className='mx_LocationViewDialog'
|
||||
onFinished={this.props.onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<BaseDialog className="mx_LocationViewDialog" onFinished={this.props.onFinished} fixedWidth={false}>
|
||||
<Map
|
||||
id={this.getBodyId()}
|
||||
centerGeoUri={geoUri}
|
||||
|
@ -73,18 +69,17 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
|
|||
interactive
|
||||
className="mx_LocationViewDialog_map"
|
||||
>
|
||||
{
|
||||
({ map }) =>
|
||||
<>
|
||||
<SmartMarker
|
||||
map={map}
|
||||
id={`${this.getBodyId()}-marker`}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
/>
|
||||
<ZoomButtons map={map} />
|
||||
</>
|
||||
}
|
||||
{({ map }) => (
|
||||
<>
|
||||
<SmartMarker
|
||||
map={map}
|
||||
id={`${this.getBodyId()}-marker`}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
/>
|
||||
<ZoomButtons map={map} />
|
||||
</>
|
||||
)}
|
||||
</Map>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode, useContext, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/matrix';
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
import React, { ReactNode, useContext, useEffect } from "react";
|
||||
import classNames from "classnames";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { parseGeoUri } from '../../../utils/location';
|
||||
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
|
||||
import { useMap } from '../../../utils/location/useMap';
|
||||
import { Bounds } from '../../../utils/beacon/bounds';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { parseGeoUri } from "../../../utils/location";
|
||||
import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
|
||||
import { useMap } from "../../../utils/location/useMap";
|
||||
import { Bounds } from "../../../utils/beacon/bounds";
|
||||
|
||||
const useMapWithStyle = ({ id, centerGeoUri, onError, interactive, bounds }) => {
|
||||
const bodyId = `mx_Map_${id}`;
|
||||
|
@ -52,7 +52,7 @@ const useMapWithStyle = ({ id, centerGeoUri, onError, interactive, bounds }) =>
|
|||
const coords = parseGeoUri(centerGeoUri);
|
||||
map.setCenter({ lon: coords.longitude, lat: coords.latitude });
|
||||
} catch (_error) {
|
||||
logger.error('Could not set map center');
|
||||
logger.error("Could not set map center");
|
||||
}
|
||||
}
|
||||
}, [map, centerGeoUri]);
|
||||
|
@ -66,7 +66,7 @@ const useMapWithStyle = ({ id, centerGeoUri, onError, interactive, bounds }) =>
|
|||
);
|
||||
map.fitBounds(lngLatBounds, { padding: 100, maxZoom: 15 });
|
||||
} catch (_error) {
|
||||
logger.error('Invalid map bounds');
|
||||
logger.error("Invalid map bounds");
|
||||
}
|
||||
}
|
||||
}, [map, bounds]);
|
||||
|
@ -92,25 +92,13 @@ interface MapProps {
|
|||
className?: string;
|
||||
onClick?: () => void;
|
||||
onError?: (error: Error) => void;
|
||||
children?: (renderProps: {
|
||||
map: maplibregl.Map;
|
||||
}) => ReactNode;
|
||||
children?: (renderProps: { map: maplibregl.Map }) => ReactNode;
|
||||
}
|
||||
|
||||
const Map: React.FC<MapProps> = ({
|
||||
bounds,
|
||||
centerGeoUri,
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
interactive,
|
||||
onError, onClick,
|
||||
}) => {
|
||||
const Map: React.FC<MapProps> = ({ bounds, centerGeoUri, children, className, id, interactive, onError, onClick }) => {
|
||||
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds });
|
||||
|
||||
const onMapClick = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
) => {
|
||||
const onMapClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
// Eat click events when clicking the attribution button
|
||||
const target = event.target as Element;
|
||||
if (target.classList.contains("maplibregl-ctrl-attrib-button")) {
|
||||
|
@ -120,12 +108,11 @@ const Map: React.FC<MapProps> = ({
|
|||
onClick && onClick();
|
||||
};
|
||||
|
||||
return <div className={classNames('mx_Map', className)}
|
||||
id={bodyId}
|
||||
onClick={onMapClick}
|
||||
>
|
||||
{ !!children && !!map && children({ map }) }
|
||||
</div>;
|
||||
return (
|
||||
<div className={classNames("mx_Map", className)} id={bodyId} onClick={onMapClick}>
|
||||
{!!children && !!map && children({ map })}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
|
|
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Icon as WarningBadge } from '../../../../res/img/element-icons/warning-badge.svg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { getLocationShareErrorMessage, LocationShareError } from '../../../utils/location';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Heading from '../typography/Heading';
|
||||
import { Icon as WarningBadge } from "../../../../res/img/element-icons/warning-badge.svg";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getLocationShareErrorMessage, LocationShareError } from "../../../utils/location";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Heading from "../typography/Heading";
|
||||
|
||||
export interface MapErrorProps {
|
||||
error: LocationShareError;
|
||||
|
@ -31,30 +31,21 @@ export interface MapErrorProps {
|
|||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const MapError: React.FC<MapErrorProps> = ({
|
||||
error,
|
||||
isMinimised,
|
||||
className,
|
||||
onFinished,
|
||||
onClick,
|
||||
}) => (
|
||||
<div data-test-id='map-rendering-error'
|
||||
className={classNames('mx_MapError', className, { 'mx_MapError_isMinimised': isMinimised })}
|
||||
export const MapError: React.FC<MapErrorProps> = ({ error, isMinimised, className, onFinished, onClick }) => (
|
||||
<div
|
||||
data-test-id="map-rendering-error"
|
||||
className={classNames("mx_MapError", className, { mx_MapError_isMinimised: isMinimised })}
|
||||
onClick={onClick}
|
||||
>
|
||||
<WarningBadge className='mx_MapError_icon' />
|
||||
<Heading className='mx_MapError_heading' size='h3'>{ _t('Unable to load map') }</Heading>
|
||||
<p className='mx_MapError_message'>
|
||||
{ getLocationShareErrorMessage(error) }
|
||||
</p>
|
||||
{ onFinished &&
|
||||
<AccessibleButton
|
||||
element='button'
|
||||
kind='primary'
|
||||
onClick={onFinished}
|
||||
>
|
||||
{ _t('OK') }
|
||||
<WarningBadge className="mx_MapError_icon" />
|
||||
<Heading className="mx_MapError_heading" size="h3">
|
||||
{_t("Unable to load map")}
|
||||
</Heading>
|
||||
<p className="mx_MapError_message">{getLocationShareErrorMessage(error)}</p>
|
||||
{onFinished && (
|
||||
<AccessibleButton element="button" kind="primary" onClick={onFinished}>
|
||||
{_t("OK")}
|
||||
</AccessibleButton>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Icon as LocationMarkerIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import { Icon as MapFallbackImage } from '../../../../res/img/location/map.svg';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import { Icon as LocationMarkerIcon } from "../../../../res/img/element-icons/location.svg";
|
||||
import { Icon as MapFallbackImage } from "../../../../res/img/location/map.svg";
|
||||
import Spinner from "../elements/Spinner";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
|
@ -28,11 +28,13 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||
}
|
||||
|
||||
const MapFallback: React.FC<Props> = ({ className, isLoading, children, ...rest }) => {
|
||||
return <div className={classNames('mx_MapFallback', className)} {...rest}>
|
||||
<MapFallbackImage className='mx_MapFallback_bg' />
|
||||
{ isLoading ? <Spinner h={32} w={32} /> : <LocationMarkerIcon className='mx_MapFallback_icon' /> }
|
||||
{ children }
|
||||
</div>;
|
||||
return (
|
||||
<div className={classNames("mx_MapFallback", className)} {...rest}>
|
||||
<MapFallbackImage className="mx_MapFallback_bg" />
|
||||
{isLoading ? <Spinner h={32} w={32} /> : <LocationMarkerIcon className="mx_MapFallback_icon" />}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapFallback;
|
||||
|
|
|
@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { RoomMember } from 'matrix-js-sdk/src/matrix';
|
||||
import React, { ReactNode, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import { Icon as LocationIcon } from "../../../../res/img/element-icons/location.svg";
|
||||
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
|
@ -36,11 +36,12 @@ interface Props {
|
|||
* tooltip is truthy
|
||||
*/
|
||||
const OptionalTooltip: React.FC<{
|
||||
tooltip?: ReactNode; children: ReactNode;
|
||||
tooltip?: ReactNode;
|
||||
children: ReactNode;
|
||||
}> = ({ tooltip, children }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
if (!tooltip) {
|
||||
return <>{ children }</>;
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
const show = () => setIsVisible(true);
|
||||
|
@ -51,40 +52,45 @@ const OptionalTooltip: React.FC<{
|
|||
setIsVisible(!isVisible);
|
||||
};
|
||||
|
||||
return <div onMouseEnter={show} onClick={toggleVisibility} onMouseLeave={hide}>
|
||||
{ children }
|
||||
{ isVisible && tooltip }
|
||||
</div>;
|
||||
return (
|
||||
<div onMouseEnter={show} onClick={toggleVisibility} onMouseLeave={hide}>
|
||||
{children}
|
||||
{isVisible && tooltip}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic location marker
|
||||
*/
|
||||
const Marker = React.forwardRef<HTMLDivElement, Props>(({ id, roomMember, useMemberColor, tooltip }, ref) => {
|
||||
const memberColorClass = useMemberColor && roomMember ? getUserNameColorClass(roomMember.userId) : '';
|
||||
return <div
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={classNames("mx_Marker", memberColorClass, {
|
||||
"mx_Marker_defaultColor": !memberColorClass,
|
||||
})}
|
||||
>
|
||||
<OptionalTooltip tooltip={tooltip}>
|
||||
<div className="mx_Marker_border">
|
||||
{ roomMember ?
|
||||
<MemberAvatar
|
||||
member={roomMember}
|
||||
width={36}
|
||||
height={36}
|
||||
viewUserOnClick={false}
|
||||
// no mxid on hover when marker has tooltip
|
||||
hideTitle={!!tooltip}
|
||||
/>
|
||||
: <LocationIcon className="mx_Marker_icon" />
|
||||
}
|
||||
</div>
|
||||
</OptionalTooltip>
|
||||
</div>;
|
||||
const memberColorClass = useMemberColor && roomMember ? getUserNameColorClass(roomMember.userId) : "";
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={classNames("mx_Marker", memberColorClass, {
|
||||
mx_Marker_defaultColor: !memberColorClass,
|
||||
})}
|
||||
>
|
||||
<OptionalTooltip tooltip={tooltip}>
|
||||
<div className="mx_Marker_border">
|
||||
{roomMember ? (
|
||||
<MemberAvatar
|
||||
member={roomMember}
|
||||
width={36}
|
||||
height={36}
|
||||
viewUserOnClick={false}
|
||||
// no mxid on hover when marker has tooltip
|
||||
hideTitle={!!tooltip}
|
||||
/>
|
||||
) : (
|
||||
<LocationIcon className="mx_Marker_icon" />
|
||||
)}
|
||||
</div>
|
||||
</OptionalTooltip>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Marker;
|
||||
|
|
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { Icon as BackIcon } from '../../../../res/img/element-icons/caret-left.svg';
|
||||
import { Icon as CloseIcon } from '../../../../res/img/element-icons/cancel-rounded.svg';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { Icon as BackIcon } from "../../../../res/img/element-icons/caret-left.svg";
|
||||
import { Icon as CloseIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
|
||||
|
||||
interface Props {
|
||||
onCancel: () => void;
|
||||
|
@ -27,26 +27,28 @@ interface Props {
|
|||
}
|
||||
|
||||
const ShareDialogButtons: React.FC<Props> = ({ onBack, onCancel, displayBack }) => {
|
||||
return <div className='mx_ShareDialogButtons'>
|
||||
{ displayBack &&
|
||||
return (
|
||||
<div className="mx_ShareDialogButtons">
|
||||
{displayBack && (
|
||||
<AccessibleButton
|
||||
className="mx_ShareDialogButtons_button left"
|
||||
data-test-id="share-dialog-buttons-back"
|
||||
onClick={onBack}
|
||||
element="button"
|
||||
>
|
||||
<BackIcon className="mx_ShareDialogButtons_button-icon" />
|
||||
</AccessibleButton>
|
||||
)}
|
||||
<AccessibleButton
|
||||
className='mx_ShareDialogButtons_button left'
|
||||
data-test-id='share-dialog-buttons-back'
|
||||
onClick={onBack}
|
||||
element='button'
|
||||
className="mx_ShareDialogButtons_button right"
|
||||
data-test-id="share-dialog-buttons-cancel"
|
||||
onClick={onCancel}
|
||||
element="button"
|
||||
>
|
||||
<BackIcon className='mx_ShareDialogButtons_button-icon' />
|
||||
<CloseIcon className="mx_ShareDialogButtons_button-icon" />
|
||||
</AccessibleButton>
|
||||
}
|
||||
<AccessibleButton
|
||||
className='mx_ShareDialogButtons_button right'
|
||||
data-test-id='share-dialog-buttons-cancel'
|
||||
onClick={onCancel}
|
||||
element='button'
|
||||
>
|
||||
<CloseIcon className='mx_ShareDialogButtons_button-icon' />
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShareDialogButtons;
|
||||
|
|
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { HTMLAttributes, useContext } from 'react';
|
||||
import React, { HTMLAttributes, useContext } from "react";
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
|
||||
import BaseAvatar from '../avatars/BaseAvatar';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Heading from '../typography/Heading';
|
||||
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import { LocationShareType } from './shareLocation';
|
||||
import StyledLiveBeaconIcon from '../beacon/StyledLiveBeaconIcon';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Heading from "../typography/Heading";
|
||||
import { Icon as LocationIcon } from "../../../../res/img/element-icons/location.svg";
|
||||
import { LocationShareType } from "./shareLocation";
|
||||
import StyledLiveBeaconIcon from "../beacon/StyledLiveBeaconIcon";
|
||||
|
||||
const UserAvatar = () => {
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
|
@ -34,63 +34,65 @@ const UserAvatar = () => {
|
|||
const avatarSize = 36;
|
||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
|
||||
return <div className={`mx_ShareType_option-icon ${LocationShareType.Own}`}>
|
||||
<BaseAvatar
|
||||
idName={userId}
|
||||
name={displayName}
|
||||
url={avatarUrl}
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
resizeMethod="crop"
|
||||
className="mx_UserMenu_userAvatar_BaseAvatar"
|
||||
/>
|
||||
</div>;
|
||||
return (
|
||||
<div className={`mx_ShareType_option-icon ${LocationShareType.Own}`}>
|
||||
<BaseAvatar
|
||||
idName={userId}
|
||||
name={displayName}
|
||||
url={avatarUrl}
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
resizeMethod="crop"
|
||||
className="mx_UserMenu_userAvatar_BaseAvatar"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type ShareTypeOptionProps = HTMLAttributes<Element> & { label: string, shareType: LocationShareType };
|
||||
const ShareTypeOption: React.FC<ShareTypeOptionProps> = ({
|
||||
onClick, label, shareType, ...rest
|
||||
}) => <AccessibleButton
|
||||
element='button'
|
||||
className='mx_ShareType_option'
|
||||
onClick={onClick}
|
||||
{...rest}>
|
||||
{ shareType === LocationShareType.Own && <UserAvatar /> }
|
||||
{ shareType === LocationShareType.Pin &&
|
||||
<LocationIcon className={`mx_ShareType_option-icon ${LocationShareType.Pin}`} /> }
|
||||
{ shareType === LocationShareType.Live &&
|
||||
<StyledLiveBeaconIcon className={`mx_ShareType_option-icon ${LocationShareType.Live}`} /> }
|
||||
type ShareTypeOptionProps = HTMLAttributes<Element> & { label: string; shareType: LocationShareType };
|
||||
const ShareTypeOption: React.FC<ShareTypeOptionProps> = ({ onClick, label, shareType, ...rest }) => (
|
||||
<AccessibleButton element="button" className="mx_ShareType_option" onClick={onClick} {...rest}>
|
||||
{shareType === LocationShareType.Own && <UserAvatar />}
|
||||
{shareType === LocationShareType.Pin && (
|
||||
<LocationIcon className={`mx_ShareType_option-icon ${LocationShareType.Pin}`} />
|
||||
)}
|
||||
{shareType === LocationShareType.Live && (
|
||||
<StyledLiveBeaconIcon className={`mx_ShareType_option-icon ${LocationShareType.Live}`} />
|
||||
)}
|
||||
|
||||
{ label }
|
||||
</AccessibleButton>;
|
||||
{label}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
setShareType: (shareType: LocationShareType) => void;
|
||||
enabledShareTypes: LocationShareType[];
|
||||
}
|
||||
const ShareType: React.FC<Props> = ({
|
||||
setShareType, enabledShareTypes,
|
||||
}) => {
|
||||
const ShareType: React.FC<Props> = ({ setShareType, enabledShareTypes }) => {
|
||||
const labels = {
|
||||
[LocationShareType.Own]: _t('My current location'),
|
||||
[LocationShareType.Live]: _t('My live location'),
|
||||
[LocationShareType.Pin]: _t('Drop a Pin'),
|
||||
[LocationShareType.Own]: _t("My current location"),
|
||||
[LocationShareType.Live]: _t("My live location"),
|
||||
[LocationShareType.Pin]: _t("Drop a Pin"),
|
||||
};
|
||||
return <div className='mx_ShareType'>
|
||||
<LocationIcon className='mx_ShareType_badge' />
|
||||
<Heading className='mx_ShareType_heading' size='h3'>{ _t("What location type do you want to share?") }</Heading>
|
||||
<div className='mx_ShareType_wrapper_options'>
|
||||
{ enabledShareTypes.map((type) =>
|
||||
<ShareTypeOption
|
||||
key={type}
|
||||
onClick={() => setShareType(type)}
|
||||
label={labels[type]}
|
||||
shareType={type}
|
||||
data-test-id={`share-location-option-${type}`}
|
||||
/>,
|
||||
) }
|
||||
return (
|
||||
<div className="mx_ShareType">
|
||||
<LocationIcon className="mx_ShareType_badge" />
|
||||
<Heading className="mx_ShareType_heading" size="h3">
|
||||
{_t("What location type do you want to share?")}
|
||||
</Heading>
|
||||
<div className="mx_ShareType_wrapper_options">
|
||||
{enabledShareTypes.map((type) => (
|
||||
<ShareTypeOption
|
||||
key={type}
|
||||
onClick={() => setShareType(type)}
|
||||
label={labels[type]}
|
||||
shareType={type}
|
||||
data-test-id={`share-location-option-${type}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
);
|
||||
};
|
||||
|
||||
export default ShareType;
|
||||
|
|
|
@ -14,28 +14,31 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import { RoomMember } from 'matrix-js-sdk/src/matrix';
|
||||
import React, { ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { createMarker, parseGeoUri } from '../../../utils/location';
|
||||
import Marker from './Marker';
|
||||
import { createMarker, parseGeoUri } from "../../../utils/location";
|
||||
import Marker from "./Marker";
|
||||
|
||||
const useMapMarker = (
|
||||
map: maplibregl.Map,
|
||||
geoUri: string,
|
||||
): { marker?: maplibregl.Marker, onElementRef: (el: HTMLDivElement) => void } => {
|
||||
): { marker?: maplibregl.Marker; onElementRef: (el: HTMLDivElement) => void } => {
|
||||
const [marker, setMarker] = useState<maplibregl.Marker>();
|
||||
|
||||
const onElementRef = useCallback((element: HTMLDivElement) => {
|
||||
if (marker || !element) {
|
||||
return;
|
||||
}
|
||||
const coords = parseGeoUri(geoUri);
|
||||
const newMarker = createMarker(coords, element);
|
||||
newMarker.addTo(map);
|
||||
setMarker(newMarker);
|
||||
}, [marker, geoUri, map]);
|
||||
const onElementRef = useCallback(
|
||||
(element: HTMLDivElement) => {
|
||||
if (marker || !element) {
|
||||
return;
|
||||
}
|
||||
const coords = parseGeoUri(geoUri);
|
||||
const newMarker = createMarker(coords, element);
|
||||
newMarker.addTo(map);
|
||||
setMarker(newMarker);
|
||||
},
|
||||
[marker, geoUri, map],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (marker) {
|
||||
|
@ -44,11 +47,14 @@ const useMapMarker = (
|
|||
}
|
||||
}, [marker, geoUri]);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (marker) {
|
||||
marker.remove();
|
||||
}
|
||||
}, [marker]);
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (marker) {
|
||||
marker.remove();
|
||||
}
|
||||
},
|
||||
[marker],
|
||||
);
|
||||
|
||||
return {
|
||||
marker,
|
||||
|
|
|
@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import React from "react";
|
||||
import maplibregl from "maplibre-gl";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { Icon as PlusIcon } from '../../../../res/img/element-icons/plus-button.svg';
|
||||
import { Icon as MinusIcon } from '../../../../res/img/element-icons/minus-button.svg';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { Icon as PlusIcon } from "../../../../res/img/element-icons/plus-button.svg";
|
||||
import { Icon as MinusIcon } from "../../../../res/img/element-icons/minus-button.svg";
|
||||
|
||||
interface Props {
|
||||
map: maplibregl.Map;
|
||||
|
@ -35,24 +35,26 @@ const ZoomButtons: React.FC<Props> = ({ map }) => {
|
|||
map.zoomOut();
|
||||
};
|
||||
|
||||
return <div className="mx_ZoomButtons">
|
||||
<AccessibleButton
|
||||
onClick={onZoomIn}
|
||||
data-test-id='map-zoom-in-button'
|
||||
title={_t("Zoom in")}
|
||||
className='mx_ZoomButtons_button'
|
||||
>
|
||||
<PlusIcon className='mx_ZoomButtons_icon' />
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
onClick={onZoomOut}
|
||||
data-test-id='map-zoom-out-button'
|
||||
title={_t("Zoom out")}
|
||||
className='mx_ZoomButtons_button'
|
||||
>
|
||||
<MinusIcon className='mx_ZoomButtons_icon' />
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
return (
|
||||
<div className="mx_ZoomButtons">
|
||||
<AccessibleButton
|
||||
onClick={onZoomIn}
|
||||
data-test-id="map-zoom-in-button"
|
||||
title={_t("Zoom in")}
|
||||
className="mx_ZoomButtons_button"
|
||||
>
|
||||
<PlusIcon className="mx_ZoomButtons_icon" />
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
onClick={onZoomOut}
|
||||
data-test-id="map-zoom-out-button"
|
||||
title={_t("Zoom out")}
|
||||
className="mx_ZoomButtons_button"
|
||||
>
|
||||
<MinusIcon className="mx_ZoomButtons_icon" />
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZoomButtons;
|
||||
|
|
|
@ -30,9 +30,9 @@ import { OwnBeaconStore } from "../../../stores/OwnBeaconStore";
|
|||
import { doMaybeLocalRoomAction } from "../../../utils/local-room";
|
||||
|
||||
export enum LocationShareType {
|
||||
Own = 'Own',
|
||||
Pin = 'Pin',
|
||||
Live = 'Live'
|
||||
Own = "Own",
|
||||
Pin = "Pin",
|
||||
Live = "Live",
|
||||
}
|
||||
|
||||
export type LocationShareProps = {
|
||||
|
@ -46,13 +46,16 @@ const DEFAULT_LIVE_DURATION = 300000;
|
|||
|
||||
export type ShareLocationFn = (props: LocationShareProps) => Promise<void>;
|
||||
|
||||
const getPermissionsErrorParams = (shareType: LocationShareType): {
|
||||
const getPermissionsErrorParams = (
|
||||
shareType: LocationShareType,
|
||||
): {
|
||||
errorMessage: string;
|
||||
modalParams: IQuestionDialogProps;
|
||||
} => {
|
||||
const errorMessage = shareType === LocationShareType.Live
|
||||
? "Insufficient permissions to start sharing your live location"
|
||||
: "Insufficient permissions to send your location";
|
||||
const errorMessage =
|
||||
shareType === LocationShareType.Live
|
||||
? "Insufficient permissions to start sharing your live location"
|
||||
: "Insufficient permissions to send your location";
|
||||
|
||||
const modalParams = {
|
||||
title: _t("You don't have permission to share locations"),
|
||||
|
@ -64,20 +67,24 @@ const getPermissionsErrorParams = (shareType: LocationShareType): {
|
|||
return { modalParams, errorMessage };
|
||||
};
|
||||
|
||||
const getDefaultErrorParams = (shareType: LocationShareType, openMenu: () => void): {
|
||||
const getDefaultErrorParams = (
|
||||
shareType: LocationShareType,
|
||||
openMenu: () => void,
|
||||
): {
|
||||
errorMessage: string;
|
||||
modalParams: IQuestionDialogProps;
|
||||
} => {
|
||||
const errorMessage = shareType === LocationShareType.Live
|
||||
? "We couldn't start sharing your live location"
|
||||
: "We couldn't send your location";
|
||||
const errorMessage =
|
||||
shareType === LocationShareType.Live
|
||||
? "We couldn't start sharing your live location"
|
||||
: "We couldn't send your location";
|
||||
const modalParams = {
|
||||
title: _t("We couldn't send your location"),
|
||||
description: _t("%(brand)s could not send your location. Please try again later.", {
|
||||
brand: SdkConfig.get().brand,
|
||||
}),
|
||||
button: _t('Try again'),
|
||||
cancelButton: _t('Cancel'),
|
||||
button: _t("Try again"),
|
||||
cancelButton: _t("Cancel"),
|
||||
onFinished: (tryAgain: boolean) => {
|
||||
if (tryAgain) {
|
||||
openMenu();
|
||||
|
@ -88,52 +95,55 @@ const getDefaultErrorParams = (shareType: LocationShareType, openMenu: () => voi
|
|||
};
|
||||
|
||||
const handleShareError = (error: Error, openMenu: () => void, shareType: LocationShareType): void => {
|
||||
const { modalParams, errorMessage } = (error as MatrixError).errcode === 'M_FORBIDDEN' ?
|
||||
getPermissionsErrorParams(shareType) :
|
||||
getDefaultErrorParams(shareType, openMenu);
|
||||
const { modalParams, errorMessage } =
|
||||
(error as MatrixError).errcode === "M_FORBIDDEN"
|
||||
? getPermissionsErrorParams(shareType)
|
||||
: getDefaultErrorParams(shareType, openMenu);
|
||||
|
||||
logger.error(errorMessage, error);
|
||||
|
||||
Modal.createDialog(QuestionDialog, modalParams);
|
||||
};
|
||||
|
||||
export const shareLiveLocation = (
|
||||
client: MatrixClient, roomId: string, displayName: string, openMenu: () => void,
|
||||
): ShareLocationFn => async ({ timeout }) => {
|
||||
const description = _t(`%(displayName)s's live location`, { displayName });
|
||||
try {
|
||||
await OwnBeaconStore.instance.createLiveBeacon(
|
||||
roomId,
|
||||
makeBeaconInfoContent(
|
||||
timeout ?? DEFAULT_LIVE_DURATION,
|
||||
true, /* isLive */
|
||||
description,
|
||||
LocationAssetType.Self,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
handleShareError(error, openMenu, LocationShareType.Live);
|
||||
}
|
||||
};
|
||||
export const shareLiveLocation =
|
||||
(client: MatrixClient, roomId: string, displayName: string, openMenu: () => void): ShareLocationFn =>
|
||||
async ({ timeout }) => {
|
||||
const description = _t(`%(displayName)s's live location`, { displayName });
|
||||
try {
|
||||
await OwnBeaconStore.instance.createLiveBeacon(
|
||||
roomId,
|
||||
makeBeaconInfoContent(
|
||||
timeout ?? DEFAULT_LIVE_DURATION,
|
||||
true /* isLive */,
|
||||
description,
|
||||
LocationAssetType.Self,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
handleShareError(error, openMenu, LocationShareType.Live);
|
||||
}
|
||||
};
|
||||
|
||||
export const shareLocation = (
|
||||
client: MatrixClient,
|
||||
roomId: string,
|
||||
shareType: LocationShareType,
|
||||
relation: IEventRelation | undefined,
|
||||
openMenu: () => void,
|
||||
): ShareLocationFn => async ({ uri, timestamp }) => {
|
||||
if (!uri) return;
|
||||
try {
|
||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null;
|
||||
const assetType = shareType === LocationShareType.Pin ? LocationAssetType.Pin : LocationAssetType.Self;
|
||||
const content = makeLocationContent(undefined, uri, timestamp, undefined, assetType);
|
||||
await doMaybeLocalRoomAction(
|
||||
roomId,
|
||||
(actualRoomId: string) => client.sendMessage(actualRoomId, threadId, content),
|
||||
client,
|
||||
);
|
||||
} catch (error) {
|
||||
handleShareError(error, openMenu, shareType);
|
||||
}
|
||||
};
|
||||
export const shareLocation =
|
||||
(
|
||||
client: MatrixClient,
|
||||
roomId: string,
|
||||
shareType: LocationShareType,
|
||||
relation: IEventRelation | undefined,
|
||||
openMenu: () => void,
|
||||
): ShareLocationFn =>
|
||||
async ({ uri, timestamp }) => {
|
||||
if (!uri) return;
|
||||
try {
|
||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null;
|
||||
const assetType = shareType === LocationShareType.Pin ? LocationAssetType.Pin : LocationAssetType.Self;
|
||||
const content = makeLocationContent(undefined, uri, timestamp, undefined, assetType);
|
||||
await doMaybeLocalRoomAction(
|
||||
roomId,
|
||||
(actualRoomId: string) => client.sendMessage(actualRoomId, threadId, content),
|
||||
client,
|
||||
);
|
||||
} catch (error) {
|
||||
handleShareError(error, openMenu, shareType);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue