Merge branch 'develop' into kegan/lists-as-keys

This commit is contained in:
kegsay 2023-01-20 16:24:18 +00:00 committed by GitHub
commit 6ec25234d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 127 additions and 77 deletions

View file

@ -336,6 +336,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
const { permalinkCreator, relation, replyToEvent } = this.props;
const composerContent = this.state.composerContent;
this.setState({ composerContent: "", initialComposerContent: "" });
dis.dispatch({ action: Action.ClearAndFocusSendMessageComposer });
await sendMessage(composerContent, this.state.isRichTextEnabled, {
mxClient: this.props.mxClient,
roomContext: this.context,
@ -343,7 +344,6 @@ export class MessageComposer extends React.Component<IProps, IState> {
relation,
replyToEvent,
});
dis.dispatch({ action: Action.ClearAndFocusSendMessageComposer });
}
};

View file

@ -743,6 +743,13 @@ export default class RoomHeader extends React.Component<IProps, IState> {
const buttons = this.props.showButtons ? this.renderButtons(isVideoRoom) : null;
let oobName = _t("Join Room");
if (this.props.oobData && this.props.oobData.name) {
oobName = this.props.oobData.name;
}
const name = this.renderName(oobName);
if (this.props.viewingCall && !isVideoRoom) {
return (
<header className="mx_RoomHeader light-panel">
@ -752,9 +759,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
>
<div className="mx_RoomHeader_avatar">{roomAvatar}</div>
{icon}
<div className="mx_RoomHeader_name mx_RoomHeader_name--textonly mx_RoomHeader_name--small">
{_t("Video call")}
</div>
{name}
{this.props.activeCall instanceof ElementCall && (
<GroupCallDuration groupCall={this.props.activeCall.groupCall} />
)}
@ -779,13 +784,6 @@ export default class RoomHeader extends React.Component<IProps, IState> {
);
}
let oobName = _t("Join Room");
if (this.props.oobData && this.props.oobData.name) {
oobName = this.props.oobData.name;
}
const name = this.renderName(oobName);
const topicElement = <RoomTopic room={this.props.room} className="mx_RoomHeader_topic" />;
const viewLabs = (): void =>

View file

@ -22,6 +22,7 @@ import { Icon as BoldIcon } from "../../../../../../res/img/element-icons/room/c
import { Icon as ItalicIcon } from "../../../../../../res/img/element-icons/room/composer/italic.svg";
import { Icon as UnderlineIcon } from "../../../../../../res/img/element-icons/room/composer/underline.svg";
import { Icon as StrikeThroughIcon } from "../../../../../../res/img/element-icons/room/composer/strikethrough.svg";
import { Icon as QuoteIcon } from "../../../../../../res/img/element-icons/room/composer/quote.svg";
import { Icon as InlineCodeIcon } from "../../../../../../res/img/element-icons/room/composer/inline_code.svg";
import { Icon as LinkIcon } from "../../../../../../res/img/element-icons/room/composer/link.svg";
import { Icon as BulletedListIcon } from "../../../../../../res/img/element-icons/room/composer/bulleted_list.svg";
@ -126,6 +127,12 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
onClick={() => composer.orderedList()}
icon={<NumberedListIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
actionState={actionStates.quote}
label={_td("Quote")}
onClick={() => composer.quote()}
icon={<QuoteIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
actionState={actionStates.inlineCode}
label={_td("Code")}

View file

@ -38,7 +38,7 @@ import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
import { aboveLeftOf, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
import { aboveRightOf, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
import { Alignment } from "../elements/Tooltip";
import { ButtonEvent } from "../elements/AccessibleButton";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
@ -81,7 +81,7 @@ const DeviceButton: FC<DeviceButtonProps> = ({
if (showMenu) {
const buttonRect = buttonRef.current!.getBoundingClientRect();
contextMenu = (
<IconizedContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
<IconizedContextMenu {...aboveRightOf(buttonRect, undefined, 10)} onFinished={closeMenu}>
<IconizedContextMenuOptionList>
{devices.map((d) => (
<IconizedContextMenuOption key={d.deviceId} label={d.label} onClick={() => selectDevice(d)} />
@ -101,6 +101,7 @@ const DeviceButton: FC<DeviceButtonProps> = ({
>
<AccessibleTooltipButton
className={`mx_CallView_deviceButton mx_CallView_deviceButton_${kind}`}
inputRef={buttonRef}
title={muted ? mutedTitle : unmutedTitle}
alignment={Alignment.Top}
onClick={toggle}
@ -109,7 +110,6 @@ const DeviceButton: FC<DeviceButtonProps> = ({
{devices.length > 1 ? (
<ContextMenuButton
className="mx_CallView_deviceListButton"
inputRef={buttonRef}
onClick={openMenu}
isExpanded={showMenu}
label={deviceListLabel}

View file

@ -63,6 +63,9 @@ export function useEventEmitter(emitter: EventEmitter | undefined, eventName: st
type Mapper<T> = (...args: any[]) => T;
/**
* {@link useEventEmitterState}
*/
export function useTypedEventEmitterState<T, Events extends string, Arguments extends ListenerMap<Events>>(
emitter: TypedEventEmitter<Events, Arguments>,
eventName: Events,
@ -71,6 +74,16 @@ export function useTypedEventEmitterState<T, Events extends string, Arguments ex
return useEventEmitterState<T>(emitter, eventName, fn);
}
/**
* Creates a state, that can be updated by events.
*
* @param emitter The emitter sending the event
* @param eventName Event name to listen for
* @param fn The callback function, that should return the state value.
* It should have the signature of the event callback, except that all parameters are optional.
* If the params are not set, a default value for the state should be returned.
* @returns State
*/
export function useEventEmitterState<T>(
emitter: EventEmitter | undefined,
eventName: string | symbol,

View file

@ -1997,9 +1997,9 @@
"Close call": "Close call",
"View chat timeline": "View chat timeline",
"Room options": "Room options",
"Join Room": "Join Room",
"(~%(count)s results)|other": "(~%(count)s results)",
"(~%(count)s results)|one": "(~%(count)s result)",
"Join Room": "Join Room",
"Video rooms are a beta feature": "Video rooms are a beta feature",
"Video room": "Video room",
"Public space": "Public space",

View file

@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState } from "react";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
import { VoiceBroadcastPlayback } from "../models/VoiceBroadcastPlayback";
import {
VoiceBroadcastPlaybacksStore,
@ -28,15 +26,11 @@ export const useCurrentVoiceBroadcastPlayback = (
): {
currentVoiceBroadcastPlayback: VoiceBroadcastPlayback | null;
} => {
const [currentVoiceBroadcastPlayback, setVoiceBroadcastPlayback] = useState(
voiceBroadcastPlaybackStore.getCurrent(),
);
useTypedEventEmitter(
const currentVoiceBroadcastPlayback = useTypedEventEmitterState(
voiceBroadcastPlaybackStore,
VoiceBroadcastPlaybacksStoreEvent.CurrentChanged,
(playback: VoiceBroadcastPlayback) => {
setVoiceBroadcastPlayback(playback);
(playback?: VoiceBroadcastPlayback) => {
return playback ?? voiceBroadcastPlaybackStore.getCurrent();
},
);

View file

@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState } from "react";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
import { VoiceBroadcastPreRecordingStore } from "../stores/VoiceBroadcastPreRecordingStore";
import { VoiceBroadcastPreRecording } from "../models/VoiceBroadcastPreRecording";
@ -25,12 +23,14 @@ export const useCurrentVoiceBroadcastPreRecording = (
): {
currentVoiceBroadcastPreRecording: VoiceBroadcastPreRecording | null;
} => {
const [currentVoiceBroadcastPreRecording, setCurrentVoiceBroadcastPreRecording] = useState(
voiceBroadcastPreRecordingStore.getCurrent(),
const currentVoiceBroadcastPreRecording = useTypedEventEmitterState(
voiceBroadcastPreRecordingStore,
"changed",
(preRecording?: VoiceBroadcastPreRecording) => {
return preRecording ?? voiceBroadcastPreRecordingStore.getCurrent();
},
);
useTypedEventEmitter(voiceBroadcastPreRecordingStore, "changed", setCurrentVoiceBroadcastPreRecording);
return {
currentVoiceBroadcastPreRecording,
};

View file

@ -14,24 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState } from "react";
import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStore, VoiceBroadcastRecordingsStoreEvent } from "..";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
export const useCurrentVoiceBroadcastRecording = (
voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore,
): {
currentVoiceBroadcastRecording: VoiceBroadcastRecording;
} => {
const [currentVoiceBroadcastRecording, setCurrentVoiceBroadcastRecording] = useState(
voiceBroadcastRecordingsStore.getCurrent(),
);
useTypedEventEmitter(
const currentVoiceBroadcastRecording = useTypedEventEmitterState(
voiceBroadcastRecordingsStore,
VoiceBroadcastRecordingsStoreEvent.CurrentChanged,
setCurrentVoiceBroadcastRecording,
(recording?: VoiceBroadcastRecording) => {
return recording ?? voiceBroadcastRecordingsStore.getCurrent();
},
);
return {

View file

@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import {
VoiceBroadcastLiveness,
VoiceBroadcastPlayback,
VoiceBroadcastPlaybackEvent,
VoiceBroadcastPlaybackState,
VoiceBroadcastPlaybackTimes,
} from "..";
export const useVoiceBroadcastPlayback = (
@ -52,24 +52,35 @@ export const useVoiceBroadcastPlayback = (
playback.toggle();
};
const [playbackState, setPlaybackState] = useState(playback.getState());
useTypedEventEmitter(
const playbackState = useTypedEventEmitterState(
playback,
VoiceBroadcastPlaybackEvent.StateChanged,
(state: VoiceBroadcastPlaybackState, _playback: VoiceBroadcastPlayback) => {
setPlaybackState(state);
(state?: VoiceBroadcastPlaybackState) => {
return state ?? playback.getState();
},
);
const [times, setTimes] = useState({
duration: playback.durationSeconds,
position: playback.timeSeconds,
timeLeft: playback.timeLeftSeconds,
});
useTypedEventEmitter(playback, VoiceBroadcastPlaybackEvent.TimesChanged, (t) => setTimes(t));
const times = useTypedEventEmitterState(
playback,
VoiceBroadcastPlaybackEvent.TimesChanged,
(t?: VoiceBroadcastPlaybackTimes) => {
return (
t ?? {
duration: playback.durationSeconds,
position: playback.timeSeconds,
timeLeft: playback.timeLeftSeconds,
}
);
},
);
const [liveness, setLiveness] = useState(playback.getLiveness());
useTypedEventEmitter(playback, VoiceBroadcastPlaybackEvent.LivenessChanged, (l) => setLiveness(l));
const liveness = useTypedEventEmitterState(
playback,
VoiceBroadcastPlaybackEvent.LivenessChanged,
(l?: VoiceBroadcastLiveness) => {
return l ?? playback.getLiveness();
},
);
return {
times,

View file

@ -16,7 +16,7 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import React, { useState } from "react";
import React from "react";
import {
VoiceBroadcastInfoState,
@ -25,7 +25,7 @@ import {
VoiceBroadcastRecordingState,
} from "..";
import QuestionDialog from "../../components/views/dialogs/QuestionDialog";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
import { _t } from "../../languageHandler";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import Modal from "../../Modal";
@ -74,17 +74,21 @@ export const useVoiceBroadcastRecording = (
}
};
const [recordingState, setRecordingState] = useState(recording.getState());
useTypedEventEmitter(
const recordingState = useTypedEventEmitterState(
recording,
VoiceBroadcastRecordingEvent.StateChanged,
(state: VoiceBroadcastInfoState, _recording: VoiceBroadcastRecording) => {
setRecordingState(state);
(state?: VoiceBroadcastRecordingState) => {
return state ?? recording.getState();
},
);
const [timeLeft, setTimeLeft] = useState(recording.getTimeLeft());
useTypedEventEmitter(recording, VoiceBroadcastRecordingEvent.TimeLeftChanged, setTimeLeft);
const timeLeft = useTypedEventEmitterState(
recording,
VoiceBroadcastRecordingEvent.TimeLeftChanged,
(t?: number) => {
return t ?? recording.getTimeLeft();
},
);
const live = (
[VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Resumed] as VoiceBroadcastRecordingState[]

View file

@ -57,7 +57,7 @@ export enum VoiceBroadcastPlaybackEvent {
InfoStateChanged = "info_state_changed",
}
type VoiceBroadcastPlaybackTimes = {
export type VoiceBroadcastPlaybackTimes = {
duration: number;
position: number;
timeLeft: number;