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

Commit 54931cb

Browse files
committed
Merge remote-tracking branch 'origin/develop' into travis/voicemessages/timeline
2 parents 8abd251 + 34c735e commit 54931cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1128
-252
lines changed

res/css/structures/_SpacePanel.scss

+5-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ $activeBorderColor: $secondary-fg-color;
7979
.mx_SpaceItem {
8080
display: inline-flex;
8181
flex-flow: wrap;
82+
83+
&.mx_SpaceItem_narrow {
84+
align-self: baseline;
85+
}
8286
}
8387

8488
.mx_SpaceItem.collapsed {
@@ -275,7 +279,7 @@ $activeBorderColor: $secondary-fg-color;
275279
.mx_SpaceButton:hover,
276280
.mx_SpaceButton:focus-within,
277281
.mx_SpaceButton_hasMenuOpen {
278-
&:not(.mx_SpaceButton_home) {
282+
&:not(.mx_SpaceButton_home):not(.mx_SpaceButton_invite) {
279283
// Hide the badge container on hover because it'll be a menu button
280284
.mx_SpacePanel_badgeContainer {
281285
width: 0;

res/css/views/rooms/_VoiceRecordComposerTile.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ limitations under the License.
4040
height: 18px;
4141
vertical-align: middle;
4242
margin-right: 7px; // distance from left edge of waveform container (container has some margin too)
43-
background-color: $muted-fg-color;
43+
background-color: $voice-record-icon-color;
4444
mask-repeat: no-repeat;
4545
mask-size: contain;
4646
mask-image: url('$(res)/img/element-icons/trashcan.svg');

res/css/views/voice_messages/_PlayPauseButton.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ limitations under the License.
1919
width: 32px;
2020
height: 32px;
2121
border-radius: 32px;
22-
background-color: $primary-bg-color;
22+
background-color: $voice-playback-button-bg-color;
2323

2424
&::before {
2525
content: '';
2626
position: absolute; // sizing varies by icon
27-
background-color: $muted-fg-color;
27+
background-color: $voice-playback-button-fg-color;
2828
mask-repeat: no-repeat;
2929
mask-size: contain;
3030
}

res/themes/dark/css/_dark.scss

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ $preview-bar-bg-color: $header-panel-bg-color;
4242
$groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82);
4343
$inverted-bg-color: $base-color;
4444

45+
$voice-record-stop-border-color: #6F7882; // "Quarterly"
46+
$voice-record-waveform-bg-color: #394049; // "Dark Tile"
47+
$voice-record-waveform-fg-color: $tertiary-fg-color;
48+
$voice-record-waveform-incomplete-fg-color: #5b646d;
49+
$voice-record-icon-color: $tertiary-fg-color;
50+
$voice-playback-button-bg-color: $tertiary-fg-color;
51+
$voice-playback-button-fg-color: $bg-color;
52+
4553
// used by AddressSelector
4654
$selected-color: $room-highlight-color;
4755

res/themes/legacy-dark/css/_legacy-dark.scss

+9
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,15 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%)
124124

125125
$groupFilterPanel-divider-color: $roomlist-header-color;
126126

127+
// See non-legacy dark for variable information
128+
$voice-record-stop-border-color: #6F7882;
129+
$voice-record-waveform-bg-color: #394049;
130+
$voice-record-waveform-fg-color: $tertiary-fg-color;
131+
$voice-record-waveform-incomplete-fg-color: #5b646d;
132+
$voice-record-icon-color: $tertiary-fg-color;
133+
$voice-playback-button-bg-color: $tertiary-fg-color;
134+
$voice-playback-button-fg-color: $bg-color;
135+
127136
$roomtile-preview-color: #9e9e9e;
128137
$roomtile-default-badge-bg-color: #61708b;
129138
$roomtile-selected-bg-color: #1A1D23;

res/themes/legacy-light/css/_legacy-light.scss

+3
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ $voice-record-waveform-bg-color: #E3E8F0;
198198
$voice-record-waveform-fg-color: $muted-fg-color;
199199
$voice-record-waveform-incomplete-fg-color: #C1C6CD;
200200
$voice-record-live-circle-color: #ff4b55;
201+
$voice-record-icon-color: $muted-fg-color;
202+
$voice-playback-button-bg-color: $primary-bg-color;
203+
$voice-playback-button-fg-color: $muted-fg-color;
201204

202205
$roomtile-preview-color: #9e9e9e;
203206
$roomtile-default-badge-bg-color: #61708b;

res/themes/light/css/_light.scss

+3
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ $voice-record-waveform-bg-color: #E3E8F0;
188188
$voice-record-waveform-fg-color: $muted-fg-color;
189189
$voice-record-waveform-incomplete-fg-color: #C1C6CD;
190190
$voice-record-live-circle-color: #ff4b55; // $warning-color, but without letting people change it in themes
191+
$voice-record-icon-color: $muted-fg-color;
192+
$voice-playback-button-bg-color: $primary-bg-color;
193+
$voice-playback-button-fg-color: $muted-fg-color;
191194

192195
$roomtile-preview-color: $secondary-fg-color;
193196
$roomtile-default-badge-bg-color: #61708b;

src/async-components/views/dialogs/security/CreateKeyBackupDialog.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
310310
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
311311
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
312312
<p>{_t(
313-
"Please enter your Security Phrase a second time to confirm.",
313+
"Enter your Security Phrase a second time to confirm it.",
314314
)}</p>
315315
<div className="mx_CreateKeyBackupDialog_primaryContainer">
316316
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">

src/async-components/views/dialogs/security/CreateSecretStorageDialog.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -647,15 +647,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
647647
}
648648
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
649649
<p>{_t(
650-
"Enter your recovery passphrase a second time to confirm it.",
650+
"Enter your Security Phrase a second time to confirm it.",
651651
)}</p>
652652
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
653653
<Field
654654
type="password"
655655
onChange={this._onPassPhraseConfirmChange}
656656
value={this.state.passPhraseConfirm}
657657
className="mx_CreateSecretStorageDialog_passPhraseField"
658-
label={_t("Confirm your recovery passphrase")}
658+
label={_t("Confirm your Security Phrase")}
659659
autoFocus={true}
660660
autoComplete="new-password"
661661
/>

src/components/structures/LeftPanel.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
416416

417417
const roomList = <RoomList
418418
onKeyDown={this.onKeyDown}
419-
resizeNotifier={null}
419+
resizeNotifier={this.props.resizeNotifier}
420420
onFocus={this.onFocus}
421421
onBlur={this.onBlur}
422422
isMinimized={this.props.isMinimized}

src/components/views/dialogs/AddExistingToSpaceDialog.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, {useContext, useState} from "react";
17+
import React, {useContext, useMemo, useState} from "react";
1818
import classNames from "classnames";
1919
import {Room} from "matrix-js-sdk/src/models/room";
2020
import {MatrixClient} from "matrix-js-sdk/src/client";
@@ -34,6 +34,7 @@ import DMRoomMap from "../../../utils/DMRoomMap";
3434
import {calculateRoomVia} from "../../../utils/permalinks/Permalinks";
3535
import StyledCheckbox from "../elements/StyledCheckbox";
3636
import MatrixClientContext from "../../../contexts/MatrixClientContext";
37+
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
3738

3839
interface IProps extends IDialogProps {
3940
matrixClient: MatrixClient;
@@ -57,6 +58,8 @@ interface IAddExistingToSpaceProps {
5758

5859
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space, selected, onChange }) => {
5960
const cli = useContext(MatrixClientContext);
61+
const visibleRooms = useMemo(() => sortRooms(cli.getVisibleRooms()), [cli]);
62+
6063
const [query, setQuery] = useState("");
6164
const lcQuery = query.toLowerCase();
6265

@@ -65,7 +68,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
6568
const existingRoomsSet = new Set(SpaceStore.instance.getChildRooms(space.roomId));
6669

6770
const joinRule = space.getJoinRule();
68-
const [spaces, rooms, dms] = cli.getVisibleRooms().reduce((arr, room) => {
71+
const [spaces, rooms, dms] = visibleRooms.reduce((arr, room) => {
6972
if (room.getMyMembership() !== "join") return arr;
7073
if (!room.name.toLowerCase().includes(lcQuery)) return arr;
7174

src/components/views/rooms/RoomList.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
539539
onResize={this.props.onResize}
540540
showSkeleton={showSkeleton}
541541
extraTiles={extraTiles}
542+
resizeNotifier={this.props.resizeNotifier}
542543
alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)}
543544
/>
544545
});

src/components/views/rooms/RoomSublist.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { ActionPayload } from "../../../dispatcher/payloads";
4444
import { Enable, Resizable } from "re-resizable";
4545
import { Direction } from "re-resizable/lib/resizer";
4646
import { polyfillTouchEvent } from "../../../@types/polyfill";
47+
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
4748
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
4849
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
4950
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
@@ -75,7 +76,7 @@ interface IProps {
7576
onResize: () => void;
7677
showSkeleton?: boolean;
7778
alwaysVisible?: boolean;
78-
79+
resizeNotifier: ResizeNotifier;
7980
extraTiles?: ReactComponentElement<typeof ExtraTile>[];
8081

8182
// TODO: Account for https://github.com/vector-im/element-web/issues/14179
@@ -528,6 +529,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
528529
tiles.push(<RoomTile
529530
room={room}
530531
key={`room-${room.roomId}`}
532+
resizeNotifier={this.props.resizeNotifier}
531533
showMessagePreview={this.layout.showPreviews}
532534
isMinimized={this.props.isMinimized}
533535
tag={this.props.tagId}

src/components/views/rooms/RoomTile.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,14 @@ import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/Community
5353
import { replaceableComponent } from "../../../utils/replaceableComponent";
5454
import { getUnsentMessages } from "../../structures/RoomStatusBar";
5555
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
56+
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
5657

5758
interface IProps {
5859
room: Room;
5960
showMessagePreview: boolean;
6061
isMinimized: boolean;
6162
tag: TagID;
63+
resizeNotifier: ResizeNotifier;
6264
}
6365

6466
type PartialDOMRect = Pick<DOMRect, "left" | "bottom">;
@@ -102,6 +104,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
102104
};
103105
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
104106
this.roomProps = EchoChamber.forRoom(this.props.room);
107+
if (this.props.resizeNotifier) {
108+
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
109+
}
105110
}
106111

107112
private countUnsentEvents(): number {
@@ -116,6 +121,12 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
116121
this.forceUpdate(); // notification state changed - update
117122
};
118123

124+
private onResize = () => {
125+
if (this.showMessagePreview && !this.state.messagePreview) {
126+
this.setState({messagePreview: this.generatePreview()});
127+
}
128+
};
129+
119130
private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => {
120131
if (!room?.roomId === this.props.room.roomId) return;
121132
this.setState({hasUnsentEvents: this.countUnsentEvents() > 0});
@@ -195,6 +206,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
195206
);
196207
this.props.room.off("Room.name", this.onRoomNameUpdate);
197208
}
209+
if (this.props.resizeNotifier) {
210+
this.props.resizeNotifier.off("middlePanelResized", this.onResize);
211+
}
198212
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
199213
defaultDispatcher.unregister(this.dispatcherRef);
200214
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);

src/components/views/rooms/VoiceRecordComposerTile.tsx

+54-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
2828
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
2929
import RecordingPlayback from "../voice_messages/RecordingPlayback";
3030
import {MsgType} from "matrix-js-sdk/src/@types/event";
31+
import Modal from "../../../Modal";
32+
import ErrorDialog from "../dialogs/ErrorDialog";
33+
import CallMediaHandler from "../../../CallMediaHandler";
3134

3235
interface IProps {
3336
room: Room;
@@ -113,16 +116,59 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
113116
await this.state.recorder.stop();
114117
return;
115118
}
116-
const recorder = VoiceRecordingStore.instance.startRecording();
117-
await recorder.start();
118119

119-
// We don't need to remove the listener: the recorder will clean that up for us.
120-
recorder.on(UPDATE_EVENT, (ev: RecordingState) => {
121-
if (ev === RecordingState.EndingSoon) return; // ignore this state: it has no UI purpose here
122-
this.setState({recordingPhase: ev});
123-
});
120+
// The "microphone access error" dialogs are used a lot, so let's functionify them
121+
const accessError = () => {
122+
Modal.createTrackedDialog('Microphone Access Error', '', ErrorDialog, {
123+
title: _t("Unable to access your microphone"),
124+
description: <>
125+
<p>{_t(
126+
"We were unable to access your microphone. Please check your browser settings and try again.",
127+
)}</p>
128+
</>,
129+
});
130+
};
131+
132+
// Do a sanity test to ensure we're about to grab a valid microphone reference. Things might
133+
// change between this and recording, but at least we will have tried.
134+
try {
135+
const devices = await CallMediaHandler.getDevices();
136+
if (!devices?.['audioinput']?.length) {
137+
Modal.createTrackedDialog('No Microphone Error', '', ErrorDialog, {
138+
title: _t("No microphone found"),
139+
description: <>
140+
<p>{_t(
141+
"We didn't find a microphone on your device. Please check your settings and try again.",
142+
)}</p>
143+
</>,
144+
});
145+
return;
146+
}
147+
// else we probably have a device that is good enough
148+
} catch (e) {
149+
console.error("Error getting devices: ", e);
150+
accessError();
151+
return;
152+
}
153+
154+
try {
155+
const recorder = VoiceRecordingStore.instance.startRecording();
156+
await recorder.start();
157+
158+
// We don't need to remove the listener: the recorder will clean that up for us.
159+
recorder.on(UPDATE_EVENT, (ev: RecordingState) => {
160+
if (ev === RecordingState.EndingSoon) return; // ignore this state: it has no UI purpose here
161+
this.setState({recordingPhase: ev});
162+
});
163+
164+
this.setState({recorder, recordingPhase: RecordingState.Started});
165+
} catch (e) {
166+
console.error("Error starting recording: ", e);
167+
accessError();
124168

125-
this.setState({recorder, recordingPhase: RecordingState.Started});
169+
// noinspection ES6MissingAwait - if this goes wrong we don't want it to affect the call stack
170+
VoiceRecordingStore.instance.disposeRecording();
171+
}
126172
};
127173

128174
private renderWaveformArea(): ReactNode {

src/components/views/spaces/SpaceTreeLevel.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,19 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
297297
const isActive = activeSpaces.includes(space);
298298
const itemClasses = classNames({
299299
"mx_SpaceItem": true,
300+
"mx_SpaceItem_narrow": isNarrow,
300301
"collapsed": collapsed,
301302
"hasSubSpaces": childSpaces && childSpaces.length,
302303
});
304+
305+
const isInvite = space.getMyMembership() === "invite";
303306
const classes = classNames("mx_SpaceButton", {
304307
mx_SpaceButton_active: isActive,
305308
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
306309
mx_SpaceButton_narrow: isNarrow,
310+
mx_SpaceButton_invite: isInvite,
307311
});
308-
const notificationState = space.getMyMembership() === "invite"
312+
const notificationState = isInvite
309313
? StaticNotificationState.forSymbol("!", NotificationColor.Red)
310314
: SpaceStore.instance.getNotificationState(space.roomId);
311315

src/editor/model.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export default class EditorModel {
158158
}
159159
}
160160

161-
reset(serializedParts: SerializedPart[], caret: Caret, inputType: string) {
161+
reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string) {
162162
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
163163
if (!caret) {
164164
caret = this.getPositionAtEnd();

0 commit comments

Comments
 (0)