Add Voice Broadcast labs setting and composer button (#9279)
* Add Voice Broadcast labs setting and composer button * Implement strict typing * Extend MessageComposer-test * Extend tests * Revert some strict type fixex * Convert FEATURES to enum; change case * Use fake timers in MessageComposer-test
This commit is contained in:
parent
4a23630e06
commit
a0c35d088a
13 changed files with 469 additions and 45 deletions
|
@ -52,6 +52,7 @@ import MessageComposerButtons from './MessageComposerButtons';
|
|||
import { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
|
||||
import { Features } from '../../../settings/Settings';
|
||||
|
||||
let instanceCount = 0;
|
||||
|
||||
|
@ -89,10 +90,11 @@ interface IState {
|
|||
isStickerPickerOpen: boolean;
|
||||
showStickersButton: boolean;
|
||||
showPollsButton: boolean;
|
||||
showVoiceBroadcastButton: boolean;
|
||||
}
|
||||
|
||||
export default class MessageComposer extends React.Component<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
private dispatcherRef?: string;
|
||||
private messageComposerInput = createRef<SendMessageComposerClass>();
|
||||
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
|
||||
private ref: React.RefObject<HTMLDivElement> = createRef();
|
||||
|
@ -114,17 +116,19 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
this.state = {
|
||||
isComposerEmpty: true,
|
||||
haveRecording: false,
|
||||
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
|
||||
recordingTimeLeftSeconds: undefined, // when set to a number, shows a toast
|
||||
isMenuOpen: false,
|
||||
isStickerPickerOpen: false,
|
||||
showStickersButton: SettingsStore.getValue("MessageComposerInput.showStickersButton"),
|
||||
showPollsButton: SettingsStore.getValue("MessageComposerInput.showPollsButton"),
|
||||
showVoiceBroadcastButton: SettingsStore.getValue(Features.VoiceBroadcast),
|
||||
};
|
||||
|
||||
this.instanceId = instanceCount++;
|
||||
|
||||
SettingsStore.monitorSetting("MessageComposerInput.showStickersButton", null);
|
||||
SettingsStore.monitorSetting("MessageComposerInput.showPollsButton", null);
|
||||
SettingsStore.monitorSetting(Features.VoiceBroadcast, null);
|
||||
}
|
||||
|
||||
private get voiceRecording(): Optional<VoiceRecording> {
|
||||
|
@ -153,7 +157,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
public componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.waitForOwnMember();
|
||||
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
|
||||
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current!);
|
||||
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
|
||||
this.updateRecordingState(); // grab any cached recordings
|
||||
}
|
||||
|
@ -199,13 +203,19 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case Features.VoiceBroadcast: {
|
||||
if (this.state.showVoiceBroadcastButton !== settingUpdatedPayload.newValue) {
|
||||
this.setState({ showVoiceBroadcastButton: !!settingUpdatedPayload.newValue });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private waitForOwnMember() {
|
||||
// if we have the member already, do that
|
||||
// If we have the member already, do that
|
||||
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
|
||||
if (me) {
|
||||
this.setState({ me });
|
||||
|
@ -242,6 +252,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
const viaServers = [this.context.tombstone.getSender().split(':').slice(1).join(':')];
|
||||
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
highlighted: true,
|
||||
|
@ -426,8 +437,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
let recordingTooltip;
|
||||
const secondsLeft = Math.round(this.state.recordingTimeLeftSeconds);
|
||||
if (secondsLeft) {
|
||||
if (this.state.recordingTimeLeftSeconds) {
|
||||
const secondsLeft = Math.round(this.state.recordingTimeLeftSeconds);
|
||||
recordingTooltip = <Tooltip
|
||||
label={_t("%(seconds)ss left", { seconds: secondsLeft })}
|
||||
alignment={Alignment.Top}
|
||||
|
@ -484,6 +495,14 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
|||
showPollsButton={this.state.showPollsButton}
|
||||
showStickersButton={this.showStickersButton}
|
||||
toggleButtonMenu={this.toggleButtonMenu}
|
||||
showVoiceBroadcastButton={this.state.showVoiceBroadcastButton}
|
||||
onStartVoiceBroadcastClick={() => {
|
||||
// Sends a voice message. To be replaced by voice broadcast during development.
|
||||
this.voiceRecordingButton.current?.onRecordStartEndClick();
|
||||
if (this.context.narrow) {
|
||||
this.toggleButtonMenu();
|
||||
}
|
||||
}}
|
||||
/> }
|
||||
{ showSendButton && (
|
||||
<SendButton
|
||||
|
|
|
@ -45,7 +45,7 @@ interface IProps {
|
|||
haveRecording: boolean;
|
||||
isMenuOpen: boolean;
|
||||
isStickerPickerOpen: boolean;
|
||||
menuPosition: AboveLeftOf;
|
||||
menuPosition?: AboveLeftOf;
|
||||
onRecordStartEndClick: () => void;
|
||||
relation?: IEventRelation;
|
||||
setStickerPickerOpen: (isStickerPickerOpen: boolean) => void;
|
||||
|
@ -53,6 +53,8 @@ interface IProps {
|
|||
showPollsButton: boolean;
|
||||
showStickersButton: boolean;
|
||||
toggleButtonMenu: () => void;
|
||||
showVoiceBroadcastButton: boolean;
|
||||
onStartVoiceBroadcastClick: () => void;
|
||||
}
|
||||
|
||||
type OverflowMenuCloser = () => void;
|
||||
|
@ -76,7 +78,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
|||
uploadButton(), // props passed via UploadButtonContext
|
||||
showStickersButton(props),
|
||||
voiceRecordingButton(props, narrow),
|
||||
props.showPollsButton && pollButton(room, props.relation),
|
||||
startVoiceBroadcastButton(props),
|
||||
props.showPollsButton ? pollButton(room, props.relation) : null,
|
||||
showLocationButton(props, room, roomId, matrixClient),
|
||||
];
|
||||
} else {
|
||||
|
@ -87,7 +90,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
|||
moreButtons = [
|
||||
showStickersButton(props),
|
||||
voiceRecordingButton(props, narrow),
|
||||
props.showPollsButton && pollButton(room, props.relation),
|
||||
startVoiceBroadcastButton(props),
|
||||
props.showPollsButton ? pollButton(room, props.relation) : null,
|
||||
showLocationButton(props, room, roomId, matrixClient),
|
||||
];
|
||||
}
|
||||
|
@ -265,7 +269,7 @@ const UploadButton = () => {
|
|||
/>;
|
||||
};
|
||||
|
||||
function showStickersButton(props: IProps): ReactElement {
|
||||
function showStickersButton(props: IProps): ReactElement | null {
|
||||
return (
|
||||
props.showStickersButton
|
||||
? <CollapsibleButton
|
||||
|
@ -280,7 +284,21 @@ function showStickersButton(props: IProps): ReactElement {
|
|||
);
|
||||
}
|
||||
|
||||
function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement {
|
||||
const startVoiceBroadcastButton: React.FC<IProps> = (props: IProps): ReactElement | null => {
|
||||
return (
|
||||
props.showVoiceBroadcastButton
|
||||
? <CollapsibleButton
|
||||
key="start_voice_broadcast"
|
||||
className="mx_MessageComposer_button"
|
||||
iconClassName="mx_MessageComposer_voiceBroadcast"
|
||||
onClick={props.onStartVoiceBroadcastClick}
|
||||
title={_t("Voice broadcast")}
|
||||
/>
|
||||
: null
|
||||
);
|
||||
};
|
||||
|
||||
function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement | null {
|
||||
// XXX: recording UI does not work well in narrow mode, so hide for now
|
||||
return (
|
||||
narrow
|
||||
|
@ -312,7 +330,7 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
|
|||
this.context?.(); // close overflow menu
|
||||
const canSend = this.props.room.currentState.maySendEvent(
|
||||
M_POLL_START.name,
|
||||
MatrixClientPeg.get().getUserId(),
|
||||
MatrixClientPeg.get().getUserId()!,
|
||||
);
|
||||
if (!canSend) {
|
||||
Modal.createDialog(
|
||||
|
@ -362,14 +380,16 @@ function showLocationButton(
|
|||
room: Room,
|
||||
roomId: string,
|
||||
matrixClient: MatrixClient,
|
||||
): ReactElement {
|
||||
): ReactElement | null {
|
||||
const sender = room.getMember(matrixClient.getUserId()!);
|
||||
|
||||
return (
|
||||
props.showLocationButton
|
||||
props.showLocationButton && sender
|
||||
? <LocationButton
|
||||
key="location"
|
||||
roomId={roomId}
|
||||
relation={props.relation}
|
||||
sender={room.getMember(matrixClient.getUserId())}
|
||||
sender={sender}
|
||||
menuPosition={props.menuPosition}
|
||||
/>
|
||||
: null
|
||||
|
|
|
@ -34,13 +34,13 @@ function cancelQuoting(context: TimelineRenderingType) {
|
|||
|
||||
interface IProps {
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
replyToEvent: MatrixEvent;
|
||||
replyToEvent?: MatrixEvent;
|
||||
}
|
||||
|
||||
export default class ReplyPreview extends React.Component<IProps> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): JSX.Element | null {
|
||||
if (!this.props.replyToEvent) return null;
|
||||
|
||||
return <div className="mx_ReplyPreview">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue