Merge branch 'develop' into improve-layout-handling

This commit is contained in:
Šimon Brandner 2021-02-15 16:12:08 +01:00
commit 5072fb0608
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
179 changed files with 10219 additions and 5623 deletions

View file

@ -28,12 +28,13 @@ import WidgetUtils from '../../../utils/WidgetUtils';
import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import WidgetStore from "../../../stores/WidgetStore";
import ResizeHandle from "../elements/ResizeHandle";
import Resizer from "../../../resizer/resizer";
import PercentageDistributor from "../../../resizer/distributors/percentage";
import {Container, WidgetLayoutStore} from "../../../stores/widgets/WidgetLayoutStore";
import {clamp, percentageOf, percentageWithin} from "../../../utils/numbers";
import {useStateCallback} from "../../../hooks/useStateCallback";
export default class AppsDrawer extends React.Component {
static propTypes = {
@ -62,13 +63,13 @@ export default class AppsDrawer extends React.Component {
componentDidMount() {
ScalarMessaging.startListening();
WidgetStore.instance.on(this.props.room.roomId, this._updateApps);
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(this.props.room), this._updateApps);
this.dispatcherRef = dis.register(this.onAction);
}
componentWillUnmount() {
ScalarMessaging.stopListening();
WidgetStore.instance.off(this.props.room.roomId, this._updateApps);
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
if (this._resizeContainer) {
this.resizer.detach();
@ -102,11 +103,10 @@ export default class AppsDrawer extends React.Component {
},
onResizeStop: () => {
this._resizeContainer.classList.remove("mx_AppsDrawer_resizing");
// persist to localStorage
localStorage.setItem(this._getStorageKey(), JSON.stringify([
this.state.apps.map(app => app.id),
...this.state.apps.slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
]));
WidgetLayoutStore.instance.setResizerDistributions(
this.props.room, Container.Top,
this.state.apps.slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
);
},
};
// pass a truthy container for now, we won't call attach until we update it
@ -128,8 +128,6 @@ export default class AppsDrawer extends React.Component {
this._loadResizerPreferences();
};
_getStorageKey = () => `mx_apps_drawer-${this.props.room.roomId}`;
_getAppsHash = (apps) => apps.map(app => app.id).join("~");
componentDidUpdate(prevProps, prevState) {
@ -147,24 +145,16 @@ export default class AppsDrawer extends React.Component {
};
_loadResizerPreferences = () => {
try {
const [[...lastIds], ...sizes] = JSON.parse(localStorage.getItem(this._getStorageKey()));
// Every app was included in the last split, reuse the last sizes
if (this.state.apps.length <= lastIds.length && this.state.apps.every((app, i) => lastIds[i] === app.id)) {
sizes.forEach((size, i) => {
const distributor = this.resizer.forHandleAt(i);
if (distributor) {
distributor.size = size;
distributor.finish();
}
});
return;
}
} catch (e) {
// this is expected
}
if (this.state.apps) {
const distributions = WidgetLayoutStore.instance.getResizerDistributions(this.props.room, Container.Top);
if (this.state.apps && (this.state.apps.length - 1) === distributions.length) {
distributions.forEach((size, i) => {
const distributor = this.resizer.forHandleAt(i);
if (distributor) {
distributor.size = size;
distributor.finish();
}
});
} else if (this.state.apps) {
const distributors = this.resizer.getDistributors();
distributors.forEach(d => d.item.clearSize());
distributors.forEach(d => d.start());
@ -190,7 +180,7 @@ export default class AppsDrawer extends React.Component {
}
};
_getApps = () => WidgetStore.instance.getPinnedApps(this.props.room.roomId);
_getApps = () => WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top);
_updateApps = () => {
this.setState({
@ -248,7 +238,7 @@ export default class AppsDrawer extends React.Component {
return (
<div className={classes}>
<PersistentVResizer
id={"apps-drawer_" + this.props.room.roomId}
room={this.props.room}
minHeight={100}
maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
handleClass="mx_AppsContainer_resizerHandle"
@ -273,7 +263,7 @@ export default class AppsDrawer extends React.Component {
}
const PersistentVResizer = ({
id,
room,
minHeight,
maxHeight,
className,
@ -282,7 +272,24 @@ const PersistentVResizer = ({
resizeNotifier,
children,
}) => {
const [height, setHeight] = useLocalStorageState("pvr_" + id, 280); // old fixed height was 273px
let defaultHeight = WidgetLayoutStore.instance.getContainerHeight(room, Container.Top);
// Arbitrary defaults to avoid NaN problems. 100 px or 3/4 of the visible window.
if (!minHeight) minHeight = 100;
if (!maxHeight) maxHeight = (window.innerHeight / 4) * 3;
// Convert from percentage to height. Note that the default height is 280px.
if (defaultHeight) {
defaultHeight = clamp(defaultHeight, 0, 100);
defaultHeight = percentageWithin(defaultHeight / 100, minHeight, maxHeight);
} else {
defaultHeight = 280;
}
const [height, setHeight] = useStateCallback(defaultHeight, newHeight => {
newHeight = percentageOf(newHeight, minHeight, maxHeight) * 100;
WidgetLayoutStore.instance.setContainerHeight(room, Container.Top, newHeight);
});
return <Resizable
size={{height: Math.min(height, maxHeight)}}

View file

@ -519,7 +519,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private async tabCompleteName(event: React.KeyboardEvent) {
try {
await new Promise(resolve => this.setState({showVisualBell: false}, resolve));
await new Promise<void>(resolve => this.setState({showVisualBell: false}, resolve));
const {model} = this.props;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);

View file

@ -38,6 +38,7 @@ import {E2E_STATE} from "./E2EIcon";
import {toRem} from "../../../utils/units";
import {WidgetType} from "../../../widgets/WidgetType";
import RoomAvatar from "../avatars/RoomAvatar";
import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStore";
const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
@ -66,6 +67,7 @@ const stateEventTileTypes = {
'm.room.server_acl': 'messages.TextualEvent',
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
'im.vector.modular.widgets': 'messages.TextualEvent',
[WIDGET_LAYOUT_EVENT_TYPE]: 'messages.TextualEvent',
'm.room.tombstone': 'messages.TextualEvent',
'm.room.join_rules': 'messages.TextualEvent',
'm.room.guest_access': 'messages.TextualEvent',

View file

@ -109,9 +109,12 @@ function HangupButton(props) {
dis.dispatch({
action,
// hangup the call for this room, which may not be the room in props
// (e.g. conferences which will hangup the 1:1 room instead)
room_id: call.roomId,
// hangup the call for this room. NB. We use the room in props as the room ID
// as call.roomId may be the 'virtual room', and the dispatch actions always
// use the user-facing room (there was a time when we deliberately used
// call.roomId and *not* props.roomId, but that was for the old
// style Freeswitch conference calls and those times are gone.)
room_id: props.roomId,
});
};
@ -423,7 +426,8 @@ export default class MessageComposer extends React.Component {
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
);
if (SettingsStore.getValue(UIFeature.Widgets)) {
if (SettingsStore.getValue(UIFeature.Widgets) &&
SettingsStore.getValue("MessageComposerInput.showStickersButton")) {
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
}

View file

@ -111,7 +111,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
appear={true} in={this.state.doAnimation} timeout={640}
classNames='mx_RoomBreadcrumbs'
>
<Toolbar className='mx_RoomBreadcrumbs'>
<Toolbar className='mx_RoomBreadcrumbs' aria-label={_t("Recently visited rooms")}>
{tiles.slice(this.state.skipFirst ? 1 : 0)}
</Toolbar>
</CSSTransition>

View file

@ -455,8 +455,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
const unfilteredLists = RoomListStore.instance.unfilteredLists
const unfilteredRooms = unfilteredLists[DefaultTagID.Untagged] || [];
const unfilteredHistorical = unfilteredLists[DefaultTagID.Archived] || [];
const unfilteredFavourite = unfilteredLists[DefaultTagID.Favourite] || [];
// show a prompt to join/create rooms if the user is in 0 rooms and no historical
if (unfilteredRooms.length < 1 && unfilteredHistorical < 1) {
if (unfilteredRooms.length < 1 && unfilteredHistorical < 1 && unfilteredFavourite < 1) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Use the + to make a new room or explore existing ones below")}</div>
<AccessibleButton

View file

@ -110,6 +110,11 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
this.onCommunityUpdate,
);
this.props.room.on("Room.name", this.onRoomNameUpdate);
}
private onRoomNameUpdate = (room) => {
this.forceUpdate();
}
private onNotificationUpdate = () => {
@ -150,6 +155,8 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
CommunityPrototypeStore.getUpdateEventName(this.props.room?.roomId),
this.onCommunityUpdate,
);
prevProps.room?.off("Room.name", this.onRoomNameUpdate);
this.props.room?.on("Room.name", this.onRoomNameUpdate);
}
}
@ -171,6 +178,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
this.onCommunityUpdate,
);
this.props.room.off("Room.name", this.onRoomNameUpdate);
}
defaultDispatcher.unregister(this.dispatcherRef);
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);

View file

@ -156,13 +156,14 @@ export default class SendMessageComposer extends React.Component {
this.onVerticalArrow(event, true);
} else if (event.key === Key.ARROW_DOWN) {
this.onVerticalArrow(event, false);
} else if (this._prepareToEncrypt) {
this._prepareToEncrypt();
} else if (event.key === Key.ESCAPE) {
dis.dispatch({
action: 'reply_to_event',
event: null,
});
} else if (this._prepareToEncrypt) {
// This needs to be last!
this._prepareToEncrypt();
}
};
@ -402,6 +403,7 @@ export default class SendMessageComposer extends React.Component {
this._editorRef.clearUndoHistory();
this._editorRef.focus();
this._clearStoredEditorState();
dis.dispatch({action: "scroll_to_bottom"});
}
componentWillUnmount() {

View file

@ -264,7 +264,7 @@ export default class Stickerpicker extends React.Component {
width: this.popoverWidth,
}}
>
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} style={{zIndex: STICKERPICKER_Z_INDEX}}>
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}>
<AppTile
app={stickerApp}
room={this.props.room}

View file

@ -81,7 +81,7 @@ export default class WhoIsTypingTile extends React.Component {
};
onRoomTimeline = (event, room) => {
if (room && room.roomId === this.props.room.roomId) {
if (room?.roomId === this.props.room?.roomId) {
const userId = event.getSender();
// remove user from usersTyping
const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId);