Merge pull request #6753 from matrix-org/revert-6752-revert-6682-gsouquet/compact-composer-18533

pull/21833/head
Germain 2021-09-09 18:49:38 +01:00 committed by GitHub
commit d475b7f1ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 127 deletions

View File

@ -733,4 +733,8 @@ $hover-select-border: 4px;
padding-bottom: 5px;
margin-bottom: 5px;
}
.mx_MessageComposer_sendMessage {
margin-right: 0;
}
}

View File

@ -186,11 +186,14 @@ limitations under the License.
}
.mx_MessageComposer_button {
--size: 26px;
position: relative;
margin-right: 6px;
cursor: pointer;
height: 26px;
width: 26px;
height: var(--size);
line-height: var(--size);
width: auto;
padding-left: calc(var(--size) + 5px);
border-radius: 100%;
&::before {
@ -207,8 +210,22 @@ limitations under the License.
mask-position: center;
}
&:hover {
background: rgba($accent-color, 0.1);
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: var(--size);
height: var(--size);
border-radius: 50%;
}
&:hover,
&.mx_MessageComposer_closeButtonMenu {
&::after {
background: rgba($accent-color, 0.1);
}
&::before {
background-color: $accent-color;
@ -237,10 +254,18 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg');
}
.mx_MessageComposer_buttonMenu::before {
mask-image: url('$(res)/img/image-view/more.svg');
}
.mx_MessageComposer_closeButtonMenu::before {
transform: rotate(90deg);
transform-origin: center;
}
.mx_MessageComposer_sendMessage {
cursor: pointer;
position: relative;
margin-right: 6px;
width: 32px;
height: 32px;
border-radius: 100%;
@ -349,10 +374,19 @@ limitations under the License.
margin-right: 0;
.mx_MessageComposer_wrapper {
padding: 0;
padding: 0 0 0 25px;
}
.mx_MessageComposer_button:last-child {
margin-right: 0;
}
.mx_MessageComposer_e2eIcon {
left: 0;
}
}
.mx_MessageComposer_Menu .mx_CallContextMenu_item {
display: flex;
align-items: center;
}

View File

@ -53,6 +53,7 @@ import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
import { throttle } from 'lodash';
import SpaceStore from "../../stores/SpaceStore";
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import { E2EStatus } from '../../utils/ShieldUtils';
interface IProps {
room?: Room; // if showing panels for a given room, this is set
@ -60,6 +61,7 @@ interface IProps {
user?: User; // used if we know the user ahead of opening the panel
resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
}
interface IState {
@ -319,7 +321,8 @@ export default class RightPanel extends React.Component<IProps, IState> {
resizeNotifier={this.props.resizeNotifier}
onClose={this.onClose}
mxEvent={this.state.event}
permalinkCreator={this.props.permalinkCreator} />;
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus} />;
break;
case RightPanelPhases.ThreadPanel:

View File

@ -2063,7 +2063,8 @@ export default class RoomView extends React.Component<IProps, IState> {
? <RightPanel
room={this.state.room}
resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} />
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
e2eStatus={this.state.e2eStatus} />
: null;
const timelineClasses = classNames("mx_RoomView_timeline", {

View File

@ -33,6 +33,7 @@ import { ActionPayload } from '../../dispatcher/payloads';
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
import { Action } from '../../dispatcher/actions';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import { E2EStatus } from '../../utils/ShieldUtils';
interface IProps {
room: Room;
@ -40,6 +41,7 @@ interface IProps {
resizeNotifier: ResizeNotifier;
mxEvent: MatrixEvent;
permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
}
interface IState {
@ -144,6 +146,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
compact={true}
/>
</BaseCard>

View File

@ -25,6 +25,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
title: string;
tooltip?: React.ReactNode;
label?: React.ReactNode;
tooltipClassName?: string;
forceHide?: boolean;
yOffset?: number;
@ -84,7 +85,8 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
aria-label={title}
>
{ children }
{ tip }
{ this.props.label }
{ (tooltip || title) && tip }
</AccessibleButton>
);
}

View File

@ -13,7 +13,7 @@ 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 React, { createRef } from 'react';
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -27,7 +27,12 @@ import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalin
import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore";
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import {
aboveLeftOf,
ContextMenu,
useContextMenu,
MenuItem,
} from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview";
import { UIFeature } from "../../../settings/UIFeature";
@ -45,6 +50,10 @@ import { Action } from "../../../dispatcher/actions";
import EditorModel from "../../../editor/model";
import EmojiPicker from '../emojipicker/EmojiPicker';
import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar";
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
let instanceCount = 0;
const NARROW_MODE_BREAKPOINT = 500;
interface IComposerAvatarProps {
me: object;
@ -71,13 +80,19 @@ function SendButton(props: ISendButtonProps) {
);
}
const EmojiButton = ({ addEmoji }) => {
interface IEmojiButtonProps {
addEmoji: (unicode: string) => boolean;
menuPosition: any; // TODO: Types
narrowMode: boolean;
}
const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narrowMode }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
let contextMenu;
if (menuDisplayed) {
const buttonRect = button.current.getBoundingClientRect();
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
</ContextMenu>;
}
@ -93,12 +108,11 @@ const EmojiButton = ({ addEmoji }) => {
// TODO: replace ContextMenuTooltipButton with a unified representation of
// the header buttons and the right panel buttons
return <React.Fragment>
<ContextMenuTooltipButton
<AccessibleTooltipButton
className={className}
onClick={openMenu}
isExpanded={menuDisplayed}
title={_t('Emoji picker')}
inputRef={button}
title={!narrowMode && _t('Emoji picker')}
label={narrowMode && _t("Add emoji")}
/>
{ contextMenu }
@ -196,6 +210,9 @@ interface IState {
haveRecording: boolean;
recordingTimeLeftSeconds?: number;
me?: RoomMember;
narrowMode?: boolean;
isMenuOpen: boolean;
showStickers: boolean;
}
@replaceableComponent("views.rooms.MessageComposer")
@ -203,6 +220,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private dispatcherRef: string;
private messageComposerInput: SendMessageComposer;
private voiceRecordingButton: VoiceRecordComposerTile;
private ref: React.RefObject<HTMLDivElement> = createRef();
private instanceId: number;
static defaultProps = {
replyInThread: false,
@ -220,15 +239,32 @@ export default class MessageComposer extends React.Component<IProps, IState> {
isComposerEmpty: true,
haveRecording: false,
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
isMenuOpen: false,
showStickers: false,
};
this.instanceId = instanceCount++;
}
componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
this.waitForOwnMember();
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
}
private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry) => {
if (type === UI_EVENTS.Resize) {
const narrowMode = entry.contentRect.width <= NARROW_MODE_BREAKPOINT;
this.setState({
narrowMode,
isMenuOpen: !narrowMode ? false : this.state.isMenuOpen,
showStickers: false,
});
}
};
private onAction = (payload: ActionPayload) => {
if (payload.action === 'reply_to_event') {
// add a timeout for the reply preview to be rendered, so
@ -263,6 +299,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
dis.unregister(this.dispatcherRef);
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize);
}
private onRoomStateEvents = (ev, state) => {
@ -312,7 +350,11 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private renderPlaceholderText = () => {
if (this.props.replyToEvent) {
if (this.props.e2eStatus) {
if (this.props.replyInThread && this.props.e2eStatus) {
return _t('Reply to encrypted thread…');
} else if (this.props.replyInThread) {
return _t('Reply to thread…');
} else if (this.props.e2eStatus) {
return _t('Send an encrypted reply…');
} else {
return _t('Send a reply…');
@ -326,11 +368,12 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
};
private addEmoji(emoji: string) {
private addEmoji(emoji: string): boolean {
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
text: emoji,
});
return true;
}
private sendMessage = async () => {
@ -369,6 +412,97 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
};
private shouldShowStickerPicker = (): boolean => {
return SettingsStore.getValue(UIFeature.Widgets)
&& SettingsStore.getValue("MessageComposerInput.showStickersButton")
&& !this.state.haveRecording;
};
private showStickers = (showStickers: boolean) => {
this.setState({ showStickers });
};
private toggleButtonMenu = (): void => {
this.setState({
isMenuOpen: !this.state.isMenuOpen,
});
};
private renderButtons(menuPosition): JSX.Element | JSX.Element[] {
const buttons: JSX.Element[] = [];
if (!this.state.haveRecording) {
buttons.push(
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
);
buttons.push(
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,
);
}
if (this.shouldShowStickerPicker()) {
let title;
if (!this.state.narrowMode) {
title = this.state.showStickers ? _t("Hide Stickers") : _t("Show Stickers");
}
buttons.push(
<AccessibleTooltipButton
id='stickersButton'
key="controls_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={() => this.showStickers(!this.state.showStickers)}
title={title}
label={this.state.narrowMode && _t("Send a sticker")}
/>,
);
}
if (!this.state.haveRecording && !this.state.narrowMode) {
buttons.push(
<AccessibleTooltipButton
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
onClick={() => this.voiceRecordingButton?.onRecordStartEndClick()}
title={_t("Send voice message")}
/>,
);
}
if (!this.state.narrowMode) {
return buttons;
} else {
const classnames = classNames({
mx_MessageComposer_button: true,
mx_MessageComposer_buttonMenu: true,
mx_MessageComposer_closeButtonMenu: this.state.isMenuOpen,
});
return <>
{ buttons[0] }
<AccessibleTooltipButton
className={classnames}
onClick={this.toggleButtonMenu}
title={_t("More options")}
tooltip={false}
/>
{ this.state.isMenuOpen && (
<ContextMenu
onFinished={this.toggleButtonMenu}
{...menuPosition}
menuPaddingRight={10}
menuPaddingTop={5}
menuPaddingBottom={5}
menuWidth={150}
wrapperClassName="mx_MessageComposer_Menu"
>
{ buttons.slice(1).map((button, index) => (
<MenuItem className="mx_CallContextMenu_item" key={index} onClick={this.toggleButtonMenu}>
{ button }
</MenuItem>
)) }
</ContextMenu>
) }
</>;
}
}
render() {
const controls = [
this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
@ -377,6 +511,12 @@ export default class MessageComposer extends React.Component<IProps, IState> {
null,
];
let menuPosition;
if (this.ref.current) {
const contentRect = this.ref.current.getBoundingClientRect();
menuPosition = aboveLeftOf(contentRect);
}
if (!this.state.tombstone && this.state.canSendMessages) {
controls.push(
<SendMessageComposer
@ -392,33 +532,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
/>,
);
if (!this.state.haveRecording) {
controls.push(
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
);
}
if (SettingsStore.getValue(UIFeature.Widgets) &&
SettingsStore.getValue("MessageComposerInput.showStickersButton") &&
!this.state.haveRecording) {
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
}
controls.push(<VoiceRecordComposerTile
key="controls_voice_record"
ref={c => this.voiceRecordingButton = c}
room={this.props.room} />);
if (!this.state.isComposerEmpty || this.state.haveRecording) {
controls.push(
<SendButton
key="controls_send"
onClick={this.sendMessage}
title={this.state.haveRecording ? _t("Send voice message") : undefined}
/>,
);
}
} else if (this.state.tombstone) {
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
@ -459,6 +576,15 @@ export default class MessageComposer extends React.Component<IProps, IState> {
yOffset={-50}
/>;
}
controls.push(
<Stickerpicker
room={this.props.room}
showStickers={this.state.showStickers}
setShowStickers={this.showStickers}
menuPosition={menuPosition} />,
);
const showSendButton = !this.state.isComposerEmpty || this.state.haveRecording;
const classes = classNames({
"mx_MessageComposer": true,
@ -467,7 +593,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
});
return (
<div className={classes}>
<div className={classes} ref={this.ref}>
{ recordingTooltip }
<div className="mx_MessageComposer_wrapper">
{ this.props.showReplyPreview && (
@ -475,6 +601,14 @@ export default class MessageComposer extends React.Component<IProps, IState> {
) }
<div className="mx_MessageComposer_row">
{ controls }
{ this.renderButtons(menuPosition) }
{ showSendButton && (
<SendButton
key="controls_send"
onClick={this.sendMessage}
title={this.state.haveRecording ? _t("Send voice message") : undefined}
/>
) }
</div>
</div>
</div>

View File

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import classNames from 'classnames';
import { Room } from 'matrix-js-sdk/src/models/room';
import { _t, _td } from '../../../languageHandler';
import AppTile from '../elements/AppTile';
@ -27,7 +26,6 @@ import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
import { WidgetType } from "../../../widgets/WidgetType";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { Action } from "../../../dispatcher/actions";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -44,10 +42,12 @@ const PERSISTED_ELEMENT_KEY = "stickerPicker";
interface IProps {
room: Room;
showStickers: boolean;
menuPosition?: any;
setShowStickers: (showStickers: boolean) => void;
}
interface IState {
showStickers: boolean;
imError: string;
stickerpickerX: number;
stickerpickerY: number;
@ -72,7 +72,6 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
showStickers: false,
imError: null,
stickerpickerX: null,
stickerpickerY: null,
@ -114,7 +113,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
console.warn('No widget ID specified, not disabling assets');
}
this.setState({ showStickers: false });
this.props.setShowStickers(false);
WidgetUtils.removeStickerpickerWidgets().then(() => {
this.forceUpdate();
}).catch((e) => {
@ -146,15 +145,15 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
}
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
this.sendVisibilityToWidget(this.state.showStickers);
this.sendVisibilityToWidget(this.props.showStickers);
}
private imError(errorMsg: string, e: Error): void {
console.error(errorMsg, e);
this.setState({
showStickers: false,
imError: _t(errorMsg),
});
this.props.setShowStickers(false);
}
private updateWidget = (): void => {
@ -194,12 +193,12 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
this.forceUpdate();
break;
case "stickerpicker_close":
this.setState({ showStickers: false });
this.props.setShowStickers(false);
break;
case Action.AfterRightPanelPhaseChange:
case "show_left_panel":
case "hide_left_panel":
this.setState({ showStickers: false });
this.props.setShowStickers(false);
break;
}
};
@ -338,8 +337,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
this.props.setShowStickers(true);
this.setState({
showStickers: true,
stickerpickerX: x,
stickerpickerY: y,
stickerpickerChevronOffset,
@ -351,8 +350,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
* @param {Event} ev Event that triggered the function call
*/
private onHideStickersClick = (ev: React.MouseEvent): void => {
if (this.state.showStickers) {
this.setState({ showStickers: false });
if (this.props.showStickers) {
this.props.setShowStickers(false);
}
};
@ -360,8 +359,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
* Called when the window is resized
*/
private onResize = (): void => {
if (this.state.showStickers) {
this.setState({ showStickers: false });
if (this.props.showStickers) {
this.props.setShowStickers(false);
}
};
@ -369,8 +368,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
* The stickers picker was hidden
*/
private onFinished = (): void => {
if (this.state.showStickers) {
this.setState({ showStickers: false });
if (this.props.showStickers) {
this.props.setShowStickers(false);
}
};
@ -395,54 +394,23 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
};
public render(): JSX.Element {
let stickerPicker;
let stickersButton;
const className = classNames(
"mx_MessageComposer_button",
"mx_MessageComposer_stickers",
"mx_Stickers_hideStickers",
"mx_MessageComposer_button_highlight",
);
if (this.state.showStickers) {
// Show hide-stickers button
stickersButton =
<AccessibleButton
id='stickersButton'
key="controls_hide_stickers"
className={className}
onClick={this.onHideStickersClick}
title={_t("Hide Stickers")}
/>;
if (!this.props.showStickers) return null;
stickerPicker = <ContextMenu
chevronOffset={this.state.stickerpickerChevronOffset}
chevronFace={ChevronFace.Bottom}
left={this.state.stickerpickerX}
top={this.state.stickerpickerY}
menuWidth={this.popoverWidth}
menuHeight={this.popoverHeight}
onFinished={this.onFinished}
menuPaddingTop={0}
menuPaddingLeft={0}
menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX}
>
<GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
</ContextMenu>;
} else {
// Show show-stickers button
stickersButton =
<AccessibleTooltipButton
id='stickersButton'
key="controls_show_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={this.onShowStickersClick}
title={_t("Show Stickers")}
/>;
}
return <React.Fragment>
{ stickersButton }
{ stickerPicker }
</React.Fragment>;
return <ContextMenu
chevronOffset={this.state.stickerpickerChevronOffset}
chevronFace={ChevronFace.Bottom}
left={this.state.stickerpickerX}
top={this.state.stickerpickerY}
menuWidth={this.popoverWidth}
menuHeight={this.popoverHeight}
onFinished={this.onFinished}
menuPaddingTop={0}
menuPaddingLeft={0}
menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX}
{...this.props.menuPosition}
>
<GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
</ContextMenu>;
}
}

View File

@ -20,7 +20,6 @@ import React, { ReactNode } from "react";
import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import classNames from "classnames";
import LiveRecordingWaveform from "../audio_messages/LiveRecordingWaveform";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import LiveRecordingClock from "../audio_messages/LiveRecordingClock";
@ -137,7 +136,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
await this.disposeRecording();
};
private onRecordStartEndClick = async () => {
public onRecordStartEndClick = async () => {
if (this.state.recorder) {
await this.state.recorder.stop();
return;
@ -215,27 +214,23 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
}
public render(): ReactNode {
let stopOrRecordBtn;
let deleteButton;
if (!this.state.recordingPhase || this.state.recordingPhase === RecordingState.Started) {
const classes = classNames({
'mx_MessageComposer_button': !this.state.recorder,
'mx_MessageComposer_voiceMessage': !this.state.recorder,
'mx_VoiceRecordComposerTile_stop': this.state.recorder?.isRecording,
});
if (!this.state.recordingPhase) return null;
let stopBtn;
let deleteButton;
if (this.state.recordingPhase === RecordingState.Started) {
let tooltip = _t("Send voice message");
if (!!this.state.recorder) {
tooltip = _t("Stop recording");
}
stopOrRecordBtn = <AccessibleTooltipButton
className={classes}
stopBtn = <AccessibleTooltipButton
className="mx_VoiceRecordComposerTile_stop"
onClick={this.onRecordStartEndClick}
title={tooltip}
/>;
if (this.state.recorder && !this.state.recorder?.isRecording) {
stopOrRecordBtn = null;
stopBtn = null;
}
}
@ -264,13 +259,10 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
</span>;
}
// The record button (mic icon) is meant to be on the right edge, but we also want the
// stop button to be left of the waveform area. Luckily, none of the surrounding UI is
// rendered when we're not recording, so the record button ends up in the correct spot.
return (<>
{ uploadIndicator }
{ deleteButton }
{ stopOrRecordBtn }
{ stopBtn }
{ this.renderWaveformArea() }
</>);
}

View File

@ -1559,12 +1559,19 @@
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
"Send message": "Send message",
"Emoji picker": "Emoji picker",
"Add emoji": "Add emoji",
"Upload file": "Upload file",
"Reply to encrypted thread…": "Reply to encrypted thread…",
"Reply to thread…": "Reply to thread…",
"Send an encrypted reply…": "Send an encrypted reply…",
"Send a reply…": "Send a reply…",
"Send an encrypted message…": "Send an encrypted message…",
"Send a message…": "Send a message…",
"Hide Stickers": "Hide Stickers",
"Show Stickers": "Show Stickers",
"Send a sticker": "Send a sticker",
"Send voice message": "Send voice message",
"More options": "More options",
"The conversation continues here.": "The conversation continues here.",
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
"You do not have permission to post to this room": "You do not have permission to post to this room",
@ -1726,8 +1733,6 @@
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
"Add some now": "Add some now",
"Stickerpack": "Stickerpack",
"Hide Stickers": "Hide Stickers",
"Show Stickers": "Show Stickers",
"Failed to revoke invite": "Failed to revoke invite",
"Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
"Admin Tools": "Admin Tools",