Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge branch 'develop' into EventBubbleTile
Browse files Browse the repository at this point in the history
  • Loading branch information
Kerry authored Apr 20, 2022
2 parents 88edb3c + 9a06558 commit b27e065
Show file tree
Hide file tree
Showing 24 changed files with 494 additions and 46 deletions.
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import "./_font-sizes.scss";
@import "./_font-weights.scss";
@import "./_spacing.scss";
@import "./components/views/beacon/_BeaconListItem.scss";
@import "./components/views/beacon/_BeaconStatus.scss";
@import "./components/views/beacon/_BeaconViewDialog.scss";
@import "./components/views/beacon/_DialogSidebar.scss";
Expand Down
61 changes: 61 additions & 0 deletions res/css/components/views/beacon/_BeaconListItem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
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_BeaconListItem {
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: flex-start;
padding: $spacing-12 0;

border-bottom: 1px solid $system;
}

.mx_BeaconListItem_avatarIcon {
flex: 0 0;
height: 32px;
width: 32px;
}

.mx_BeaconListItem_avatar {
flex: 0 0;
box-sizing: border-box;

margin-right: $spacing-8;
border: 2px solid $location-live-color;
}

.mx_BeaconListItem_info {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: stretch;
}

.mx_BeaconListItem_status {
// override beacon status padding
padding: 0 !important;
margin-bottom: $spacing-8;

.mx_BeaconStatus_label {
font-weight: $font-semi-bold;
}
}

.mx_BeaconListItem_lastUpdated {
color: $tertiary-content;
font-size: $font-10px;
}
4 changes: 4 additions & 0 deletions res/css/components/views/beacon/_BeaconStatus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ limitations under the License.
.mx_BeaconStatus_expiryTime {
color: $secondary-content;
}

.mx_BeaconStatus_label {
margin-bottom: 2px;
}
5 changes: 4 additions & 1 deletion res/css/components/views/beacon/_DialogSidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ limitations under the License.
height: 100%;
width: 265px;

display: flex;
flex-direction: column;

box-sizing: border-box;
padding: $spacing-16;

Expand All @@ -34,7 +37,7 @@ limitations under the License.
align-items: center;
justify-content: space-between;

flex: 0;
flex: 0 0;
margin-bottom: $spacing-16;

color: $primary-content;
Expand Down
82 changes: 82 additions & 0 deletions src/components/views/beacon/BeaconListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
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, { useContext } from 'react';
import { Beacon, BeaconEvent } from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';

import MatrixClientContext from '../../../contexts/MatrixClientContext';
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
import { humanizeTime } from '../../../utils/humanize';
import { _t } from '../../../languageHandler';
import MemberAvatar from '../avatars/MemberAvatar';
import CopyableText from '../elements/CopyableText';
import BeaconStatus from './BeaconStatus';
import { BeaconDisplayStatus } from './displayStatus';
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';

interface Props {
beacon: Beacon;
}

const BeaconListItem: React.FC<Props> = ({ beacon }) => {
const latestLocationState = useEventEmitterState(
beacon,
BeaconEvent.LocationUpdate,
() => beacon.latestLocationState,
);
const matrixClient = useContext(MatrixClientContext);
const room = matrixClient.getRoom(beacon.roomId);

if (!latestLocationState || !beacon.isLive) {
return null;
}

const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self;
const beaconMember = isSelfLocation ?
room.getMember(beacon.beaconInfoOwner) :
undefined;

const humanizedUpdateTime = humanizeTime(latestLocationState.timestamp);

return <li className='mx_BeaconListItem'>
{ isSelfLocation ?
<MemberAvatar
className='mx_BeaconListItem_avatar'
member={beaconMember}
height={32}
width={32}
/> :
<StyledLiveBeaconIcon className='mx_BeaconListItem_avatarIcon' />
}
<div className='mx_BeaconListItem_info'>
<BeaconStatus
className='mx_BeaconListItem_status'
beacon={beacon}
label={beaconMember?.name || beacon.beaconInfo.description || beacon.beaconInfoOwner}
displayStatus={BeaconDisplayStatus.Active}
>
<CopyableText
border={false}
getTextToCopy={() => latestLocationState?.uri}
/>
</BeaconStatus>
<span className='mx_BeaconListItem_lastUpdated'>{ _t("Updated %(humanizedUpdateTime)s", { humanizedUpdateTime }) }</span>
</div>
</li>;
};

export default BeaconListItem;
8 changes: 5 additions & 3 deletions src/components/views/beacon/BeaconStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { formatTime } from '../../../DateUtils';
interface Props {
displayStatus: BeaconDisplayStatus;
displayLiveTimeRemaining?: boolean;
withIcon?: boolean;
beacon?: Beacon;
label?: string;
}
Expand All @@ -45,6 +46,7 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
label,
className,
children,
withIcon,
...rest
}) => {
const isIdle = displayStatus === BeaconDisplayStatus.Loading ||
Expand All @@ -54,11 +56,11 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
{...rest}
className={classNames('mx_BeaconStatus', `mx_BeaconStatus_${displayStatus}`, className)}
>
<StyledLiveBeaconIcon
{ withIcon && <StyledLiveBeaconIcon
className='mx_BeaconStatus_icon'
withError={displayStatus === BeaconDisplayStatus.Error}
isIdle={isIdle}
/>
/> }
<div className='mx_BeaconStatus_description'>

{ displayStatus === BeaconDisplayStatus.Loading && <span>{ _t('Loading live location...') }</span> }
Expand All @@ -68,7 +70,7 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =

{ displayStatus === BeaconDisplayStatus.Active && beacon && <>
<>
{ label }
<span className='mx_BeaconStatus_label'>{ label }</span>
{ displayLiveTimeRemaining ?
<LiveTimeRemaining beacon={beacon} /> :
<BeaconExpiryTime beacon={beacon} />
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/beacon/DialogSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Icon as CloseIcon } from '../../../../res/img/image-view/close.svg';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import Heading from '../typography/Heading';
import BeaconListItem from './BeaconListItem';

interface Props {
beacons: Beacon[];
Expand All @@ -41,8 +42,7 @@ const DialogSidebar: React.FC<Props> = ({ beacons, requestClose }) => {
</AccessibleButton>
</div>
<ol className='mx_DialogSidebar_list'>
{ /* TODO nice elements */ }
{ beacons.map((beacon, index) => <li key={beacon.identifier}>{ index }</li>) }
{ beacons.map((beacon) => <BeaconListItem key={beacon.identifier} beacon={beacon} />) }
</ol>
</div>;
};
Expand Down
1 change: 1 addition & 0 deletions src/components/views/beacon/OwnBeaconStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
displayStatus={ownDisplayStatus}
label={_t('Live location enabled')}
displayLiveTimeRemaining
withIcon
{...rest}
>
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/elements/CopyableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ButtonEvent } from "./AccessibleButton";
import AccessibleTooltipButton from "./AccessibleTooltipButton";

interface IProps {
children: React.ReactNode;
children?: React.ReactNode;
getTextToCopy: () => string;
border?: boolean;
}
Expand Down
1 change: 1 addition & 0 deletions src/components/views/messages/MBeaconBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
beacon={beacon}
displayStatus={displayStatus}
label={_t('View live location')}
withIcon
/>
}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2913,6 +2913,7 @@
"Click for more info": "Click for more info",
"Beta": "Beta",
"Join the beta": "Join the beta",
"Updated %(humanizedUpdateTime)s": "Updated %(humanizedUpdateTime)s",
"Live until %(expiryTime)s": "Live until %(expiryTime)s",
"Loading live location...": "Loading live location...",
"Live location ended": "Live location ended",
Expand Down
87 changes: 87 additions & 0 deletions src/settings/handlers/AbstractLocalStorageSettingsHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright 2019 - 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 SettingsHandler from "./SettingsHandler";

/**
* Abstract settings handler wrapping around localStorage making getValue calls cheaper
* by caching the values and listening for localStorage updates from other tabs.
*/
export default abstract class AbstractLocalStorageSettingsHandler extends SettingsHandler {
private itemCache = new Map<string, any>();
private objectCache = new Map<string, object>();

protected constructor() {
super();

// Listen for storage changes from other tabs to bust the cache
window.addEventListener("storage", (e: StorageEvent) => {
if (e.key === null) {
this.itemCache.clear();
this.objectCache.clear();
} else {
this.itemCache.delete(e.key);
this.objectCache.delete(e.key);
}
});
}

protected getItem(key: string): any {
if (!this.itemCache.has(key)) {
const value = localStorage.getItem(key);
this.itemCache.set(key, value);
return value;
}

return this.itemCache.get(key);
}

protected getObject<T extends object>(key: string): T | null {
if (!this.objectCache.has(key)) {
try {
const value = JSON.parse(localStorage.getItem(key));
this.objectCache.set(key, value);
return value;
} catch (err) {
console.error("Failed to parse localStorage object", err);
return null;
}
}

return this.objectCache.get(key) as T;
}

protected setItem(key: string, value: any): void {
this.itemCache.set(key, value);
localStorage.setItem(key, value);
}

protected setObject(key: string, value: object): void {
this.objectCache.set(key, value);
localStorage.setItem(key, JSON.stringify(value));
}

// handles both items and objects
protected removeItem(key: string): void {
localStorage.removeItem(key);
this.itemCache.delete(key);
this.objectCache.delete(key);
}

public isSupported(): boolean {
return localStorage !== undefined && localStorage !== null;
}
}
Loading

0 comments on commit b27e065

Please sign in to comment.