/* Copyright 2024 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ import React, { useState, useEffect } from "react"; import { MatrixClient, Beacon, Room } from "matrix-js-sdk/src/matrix"; import * as maplibregl from "maplibre-gl"; import { Icon as LiveLocationIcon } from "../../../../res/img/location/live-location.svg"; import { useLiveBeacons } from "../../../utils/beacon/useLiveBeacons"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import BaseDialog from "../dialogs/BaseDialog"; import Map from "../location/Map"; import ZoomButtons from "../location/ZoomButtons"; import BeaconMarker from "./BeaconMarker"; import { Bounds, getBeaconBounds } from "../../../utils/beacon/bounds"; import { getGeoUri } from "../../../utils/beacon"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import DialogSidebar from "./DialogSidebar"; import DialogOwnBeaconStatus from "./DialogOwnBeaconStatus"; import BeaconStatusTooltip from "./BeaconStatusTooltip"; import MapFallback from "../location/MapFallback"; import { MapError } from "../location/MapError"; import { LocationShareError } from "../../../utils/location"; export interface IProps { roomId: Room["roomId"]; matrixClient: MatrixClient; // open the map centered on this beacon's location initialFocusedBeacon?: Beacon; onFinished(): void; } // track the 'focused time' as ts // to make it possible to refocus the same beacon // as the beacon location may change // or the map may move around interface FocusedBeaconState { ts: number; beacon?: Beacon; } const getBoundsCenter = (bounds?: Bounds): string | undefined => { if (!bounds) { return; } return getGeoUri({ latitude: (bounds.north + bounds.south) / 2, longitude: (bounds.east + bounds.west) / 2, timestamp: Date.now(), }); }; const useMapPosition = ( liveBeacons: Beacon[], { beacon, ts }: FocusedBeaconState, ): { bounds?: Bounds; centerGeoUri?: string; } => { const [bounds, setBounds] = useState(getBeaconBounds(liveBeacons)); const [centerGeoUri, setCenterGeoUri] = useState( beacon?.latestLocationState?.uri || getBoundsCenter(bounds), ); useEffect(() => { if ( // this check ignores the first initial focused beacon state // as centering logic on map zooms to show everything // instead of focusing down ts !== 0 && // only set focus to a known location beacon?.latestLocationState?.uri ) { // append custom `mxTs` parameter to geoUri // so map is triggered to refocus on this uri // event if it was previously the center geouri // but the map have moved/zoomed setCenterGeoUri(`${beacon?.latestLocationState?.uri};mxTs=${Date.now()}`); setBounds(getBeaconBounds([beacon])); } }, [beacon, ts]); return { bounds, centerGeoUri }; }; /** * Dialog to view live beacons maximised */ const BeaconViewDialog: React.FC = ({ initialFocusedBeacon, roomId, matrixClient, onFinished }) => { const liveBeacons = useLiveBeacons(roomId, matrixClient); const [focusedBeaconState, setFocusedBeaconState] = useState({ beacon: initialFocusedBeacon, ts: 0, }); const [isSidebarOpen, setSidebarOpen] = useState(false); const { bounds, centerGeoUri } = useMapPosition(liveBeacons, focusedBeaconState); const [mapDisplayError, setMapDisplayError] = useState(); // automatically open the sidebar if there is no map to see useEffect(() => { if (mapDisplayError) { setSidebarOpen(true); } }, [mapDisplayError]); const onBeaconListItemClick = (beacon: Beacon): void => { setFocusedBeaconState({ beacon, ts: Date.now() }); }; const hasOwnBeacon = liveBeacons.filter((beacon) => beacon?.beaconInfoOwner === matrixClient.getUserId()).length > 0; return ( {centerGeoUri && !mapDisplayError && ( {({ map }: { map: maplibregl.Map }) => ( <> {liveBeacons.map((beacon) => ( } /> ))} )} )} {mapDisplayError instanceof Error && ( )} {!centerGeoUri && !mapDisplayError && ( {_t("location_sharing|live_locations_empty")} {_t("action|close")} )} {isSidebarOpen ? ( setSidebarOpen(false)} /> ) : ( setSidebarOpen(true)} data-testid="beacon-view-dialog-open-sidebar" className="mx_BeaconViewDialog_viewListButton" >   {_t("action|view_list")} )} ); }; export default BeaconViewDialog;