diff --git a/res/css/_components.scss b/res/css/_components.scss index 583efed3b8..c602d26fb6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -4,6 +4,7 @@ @import "./_font-sizes.scss"; @import "./_font-weights.scss"; @import "./_spacing.scss"; +@import "./components/views/location/_LocationShareMenu.scss"; @import "./components/views/spaces/_QuickThemeSwitcher.scss"; @import "./structures/_AutoHideScrollbar.scss"; @import "./structures/_BackdropPanel.scss"; diff --git a/res/css/components/views/location/_LocationShareMenu.scss b/res/css/components/views/location/_LocationShareMenu.scss new file mode 100644 index 0000000000..f27935b6da --- /dev/null +++ b/res/css/components/views/location/_LocationShareMenu.scss @@ -0,0 +1,22 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_LocationShareMenu { + width: 375px; + height: 460px; + display: flex; + flex-direction: column; +} diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss index cc0830e7cd..082912c92d 100644 --- a/res/css/views/location/_LocationPicker.scss +++ b/res/css/views/location/_LocationPicker.scss @@ -15,11 +15,9 @@ limitations under the License. */ .mx_LocationPicker { - width: 375px; - height: 460px; - border-radius: 8px; + height: 100%; position: relative; #mx_LocationPicker_map { diff --git a/src/components/views/location/LocationButton.tsx b/src/components/views/location/LocationButton.tsx index d3f6e7cb3d..26f5bda13f 100644 --- a/src/components/views/location/LocationButton.tsx +++ b/src/components/views/location/LocationButton.tsx @@ -17,18 +17,12 @@ 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 { logger } from "matrix-js-sdk/src/logger"; -import { MatrixClient } from 'matrix-js-sdk/src/client'; -import { makeLocationContent } from "matrix-js-sdk/src/content-helpers"; import { _t } from '../../../languageHandler'; -import LocationPicker from './LocationPicker'; import { CollapsibleButton } from '../rooms/CollapsibleButton'; -import ContextMenu, { aboveLeftOf, useContextMenu, AboveLeftOf } from "../../structures/ContextMenu"; -import Modal from '../../../Modal'; -import QuestionDialog from '../dialogs/QuestionDialog'; -import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import { aboveLeftOf, useContextMenu, AboveLeftOf } from "../../structures/ContextMenu"; import { OverflowMenuContext } from "../rooms/MessageComposerButtons"; +import LocationShareMenu from './LocationShareMenu'; interface IProps { roomId: string; @@ -39,7 +33,6 @@ interface IProps { export const LocationButton: React.FC = ({ roomId, sender, menuPosition }) => { const overflowMenuCloser = useContext(OverflowMenuContext); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); - const matrixClient = useContext(MatrixClientContext); const _onFinished = (ev?: SyntheticEvent) => { closeMenu(ev); @@ -51,17 +44,12 @@ export const LocationButton: React.FC = ({ roomId, sender, menuPosition const position = menuPosition ?? aboveLeftOf( button.current.getBoundingClientRect()); - contextMenu = - - ; + sender={sender} + roomId={roomId} + openMenu={openMenu} />; } const className = classNames( @@ -83,47 +71,4 @@ export const LocationButton: React.FC = ({ roomId, sender, menuPosition ; }; -const shareLocation = (client: MatrixClient, roomId: string, openMenu: () => void) => - (uri: string, ts: number) => { - if (!uri) return false; - try { - const text = textForLocation(uri, ts, null); - client.sendMessage( - roomId, - makeLocationContent(text, uri, ts, null), - ); - } catch (e) { - logger.error("We couldn’t send your location", e); - - const analyticsAction = 'We couldn’t send your location'; - const params = { - title: _t("We couldn’t send your location"), - description: _t( - "Element could not send your location. Please try again later."), - button: _t('Try again'), - cancelButton: _t('Cancel'), - onFinished: (tryAgain: boolean) => { - if (tryAgain) { - openMenu(); - } - }, - }; - Modal.createTrackedDialog(analyticsAction, '', QuestionDialog, params); - } - return true; - }; - -export function textForLocation( - uri: string, - ts: number, - description: string | null, -): string { - const date = new Date(ts).toISOString(); - if (description) { - return `Location "${description}" ${uri} at ${date}`; - } else { - return `Location ${uri} at ${date}`; - } -} - export default LocationButton; diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 131dbf6ad0..ea3e4f51f0 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -30,7 +30,7 @@ import ErrorDialog from '../dialogs/ErrorDialog'; import { findMapStyleUrl } from '../messages/MLocationBody'; import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; -interface IProps { +export interface ILocationPickerProps { sender: RoomMember; onChoose(uri: string, ts: number): boolean; onFinished(ev?: SyntheticEvent): void; @@ -50,14 +50,14 @@ interface IState { */ @replaceableComponent("views.location.LocationPicker") -class LocationPicker extends React.Component { +class LocationPicker extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; private map?: maplibregl.Map = null; private geolocate?: maplibregl.GeolocateControl = null; private marker?: maplibregl.Marker = null; - constructor(props: IProps) { + constructor(props: ILocationPickerProps) { super(props); this.state = { diff --git a/src/components/views/location/LocationShareMenu.tsx b/src/components/views/location/LocationShareMenu.tsx new file mode 100644 index 0000000000..3954b45083 --- /dev/null +++ b/src/components/views/location/LocationShareMenu.tsx @@ -0,0 +1,52 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { SyntheticEvent, useContext } from 'react'; +import { Room } from 'matrix-js-sdk'; + +import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import ContextMenu, { AboveLeftOf } from '../../structures/ContextMenu'; +import LocationPicker, { ILocationPickerProps } from "./LocationPicker"; +import { shareLocation } from './shareLocation'; + +type Props = Omit & { + onFinished: (ev?: SyntheticEvent) => void; + menuPosition: AboveLeftOf; + openMenu: () => void; + roomId: Room["roomId"]; +}; + +const LocationShareMenu: React.FC = ({ + menuPosition, onFinished, sender, roomId, openMenu, +}) => { + const matrixClient = useContext(MatrixClientContext); + + return +
+ +
+
; +}; + +export default LocationShareMenu; diff --git a/src/components/views/location/shareLocation.ts b/src/components/views/location/shareLocation.ts new file mode 100644 index 0000000000..e7261230d0 --- /dev/null +++ b/src/components/views/location/shareLocation.ts @@ -0,0 +1,67 @@ + +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { makeLocationContent } from "matrix-js-sdk/src/content-helpers"; +import { logger } from "matrix-js-sdk/src/logger"; + +import { _t } from "../../../languageHandler"; +import Modal from "../../../Modal"; +import QuestionDialog from "../dialogs/QuestionDialog"; + +export const shareLocation = (client: MatrixClient, roomId: string, openMenu: () => void) => + (uri: string, ts: number) => { + if (!uri) return false; + try { + const text = textForLocation(uri, ts, null); + client.sendMessage( + roomId, + makeLocationContent(text, uri, ts, null), + ); + } catch (e) { + logger.error("We couldn’t send your location", e); + + const analyticsAction = 'We couldn’t send your location'; + const params = { + title: _t("We couldn’t send your location"), + description: _t( + "Element could not send your location. Please try again later."), + button: _t('Try again'), + cancelButton: _t('Cancel'), + onFinished: (tryAgain: boolean) => { + if (tryAgain) { + openMenu(); + } + }, + }; + Modal.createTrackedDialog(analyticsAction, '', QuestionDialog, params); + } + return true; + }; + +export function textForLocation( + uri: string, + ts: number, + description: string | null, +): string { + const date = new Date(ts).toISOString(); + if (description) { + return `Location "${description}" ${uri} at ${date}`; + } else { + return `Location ${uri} at ${date}`; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d70c17cb86..2facdac220 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -901,6 +901,7 @@ "Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)", "Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)", "Don't send read receipts": "Don't send read receipts", + "Location sharing - pin drop (under active development)": "Location sharing - pin drop (under active development)", "Font size": "Font size", "Use custom size": "Use custom size", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", @@ -2170,14 +2171,14 @@ "Can't load this message": "Can't load this message", "toggle event": "toggle event", "Location": "Location", - "We couldn’t send your location": "We couldn’t send your location", - "Element could not send your location. Please try again later.": "Element could not send your location. Please try again later.", "Could not fetch location": "Could not fetch location", "Share location": "Share location", "Element was denied permission to fetch your location. Please allow location access in your browser settings.": "Element was denied permission to fetch your location. Please allow location access in your browser settings.", "Failed to fetch your location. Please try again later.": "Failed to fetch your location. Please try again later.", "Timed out trying to fetch your location. Please try again later.": "Timed out trying to fetch your location. Please try again later.", "Unknown error fetching location. Please try again later.": "Unknown error fetching location. Please try again later.", + "We couldn’t send your location": "We couldn’t send your location", + "Element could not send your location. Please try again later.": "Element could not send your location. Please try again later.", "Failed to load group members": "Failed to load group members", "Filter community members": "Filter community members", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 649fcad056..a57235065b 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -399,6 +399,13 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Don't send read receipts"), default: false, }, + "feature_location_share_pin_drop": { + isFeature: true, + labsGroup: LabGroup.Messaging, + supportedLevels: LEVELS_FEATURE, + displayName: _td("Location sharing - pin drop (under active development)"), + default: false, + }, "baseFontSize": { displayName: _td("Font size"), supportedLevels: LEVELS_ACCOUNT_SETTINGS, diff --git a/test/components/views/location/LocationButton-test.tsx b/test/components/views/location/LocationButton-test.tsx index 66c9fd08bd..2737fa8354 100644 --- a/test/components/views/location/LocationButton-test.tsx +++ b/test/components/views/location/LocationButton-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import sdk from "../../../skinned-sdk"; -import { textForLocation } from "../../../../src/components/views/location/LocationButton"; +import { textForLocation } from "../../../../src/components/views/location/shareLocation"; sdk.getComponent("LocationPicker"); diff --git a/test/components/views/location/LocationShareMenu-test.tsx b/test/components/views/location/LocationShareMenu-test.tsx new file mode 100644 index 0000000000..29184a39b3 --- /dev/null +++ b/test/components/views/location/LocationShareMenu-test.tsx @@ -0,0 +1,51 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { mount } from 'enzyme'; +import { RoomMember } from 'matrix-js-sdk'; + +import '../../../skinned-sdk'; +import LocationShareMenu from '../../../../src/components/views/location/LocationShareMenu'; +import MatrixClientContext from '../../../../src/contexts/MatrixClientContext'; +import { ChevronFace } from '../../../../src/components/structures/ContextMenu'; + +describe('', () => { + const mockClient = { + on: jest.fn(), + }; + + const defaultProps = { + menuPosition: { + top: 1, left: 1, + chevronFace: ChevronFace.Bottom, + }, + onFinished: jest.fn(), + openMenu: jest.fn(), + roomId: '!room:server.org', + sender: { id: '@ernie:server.org' } as unknown as RoomMember, + }; + const getComponent = (props = {}) => + mount(, { + wrappingComponent: MatrixClientContext.Provider, + wrappingComponentProps: { value: mockClient }, + }); + + it('renders', () => { + const component = getComponent(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap b/test/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap new file mode 100644 index 0000000000..507ad4070f --- /dev/null +++ b/test/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap @@ -0,0 +1,295 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders 1`] = ` + + + +
+
+
+
+
+
+
+
+ Failed to load map +
+ +
+
+ + +
+ +
+
+
+
+
+
+ } + > + +
+
+
+
+
+ +
+
+
+ Failed to load map +
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + +
+ +
+
+ +
+
+
+ + + + +`;