mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
Merge branch 'develop' into element
This commit is contained in:
commit
952200f031
14 changed files with 462 additions and 243 deletions
|
@ -55,7 +55,11 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations
|
|||
flex-direction: column;
|
||||
|
||||
.mx_LeftPanel2_userHeader {
|
||||
padding: 12px 12px 20px; // 12px top, 12px sides, 20px bottom
|
||||
/* 12px top, 12px sides, 20px bottom (using 13px bottom to account
|
||||
* for internal whitespace in the breadcrumbs)
|
||||
*/
|
||||
padding: 12px 12px 13px;
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
// Create another flexbox column for the rows to stack within
|
||||
display: flex;
|
||||
|
@ -73,7 +77,20 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations
|
|||
width: 100%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
margin-top: 8px;
|
||||
margin-top: 20px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&.mx_IndicatorScrollbar_leftOverflow {
|
||||
mask-image: linear-gradient(90deg, transparent, black 10%);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_rightOverflow {
|
||||
mask-image: linear-gradient(90deg, black, black 90%, transparent);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_rightOverflow.mx_IndicatorScrollbar_leftOverflow {
|
||||
mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,6 +98,8 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations
|
|||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
// Create a flexbox to organize the inputs
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -24,6 +24,8 @@ limitations under the License.
|
|||
margin-left: 8px;
|
||||
width: 100%;
|
||||
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
.mx_RoomSublist2_headerContainer {
|
||||
// Create a flexbox to make alignment easy
|
||||
display: flex;
|
||||
|
@ -181,7 +183,6 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomSublist2_resizeBox {
|
||||
margin-bottom: 4px; // for the resize handle
|
||||
position: relative;
|
||||
|
||||
// Create another flexbox column for the tiles
|
||||
|
@ -189,55 +190,22 @@ limitations under the License.
|
|||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.mx_RoomSublist2_placeholder {
|
||||
height: 44px; // Height of a room tile plus margins
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showNButton {
|
||||
cursor: pointer;
|
||||
font-size: $font-13px;
|
||||
line-height: $font-18px;
|
||||
color: $roomtile2-preview-color;
|
||||
|
||||
// Update the render() function for RoomSublist2 if these change
|
||||
// Update the ListLayout class for minVisibleTiles if these change.
|
||||
//
|
||||
// At 24px high, 8px padding on the top and 4px padding on the bottom this equates to 0.73 of
|
||||
// a tile due to how the padding calculations work.
|
||||
height: 24px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
// We force this to the bottom so it will overlap rooms as needed.
|
||||
// We account for the space it takes up (24px) in the code through padding.
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
// We create a flexbox to cheat at alignment
|
||||
.mx_RoomSublist2_tiles {
|
||||
flex: 1 0 0;
|
||||
overflow: hidden;
|
||||
// need this to be flex otherwise the overflow hidden from above
|
||||
// sometimes vertically centers the clipped list ... no idea why it would do this
|
||||
// as the box model should be top aligned. Happens in both FF and Chromium
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomSublist2_showNButtonChevron {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 12px;
|
||||
margin-right: 18px;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $roomtile2-preview-color;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showMoreButtonChevron {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||
.mx_RoomSublist2_resizerHandles_showNButton {
|
||||
flex: 0 0 32px;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showLessButtonChevron {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
|
||||
}
|
||||
.mx_RoomSublist2_resizerHandles {
|
||||
flex: 0 0 4px;
|
||||
}
|
||||
|
||||
// Class name comes from the ResizableBox component
|
||||
|
@ -269,6 +237,42 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showNButton {
|
||||
cursor: pointer;
|
||||
font-size: $font-13px;
|
||||
line-height: $font-18px;
|
||||
color: $roomtile2-preview-color;
|
||||
|
||||
// Update the render() function for RoomSublist2 if these change
|
||||
// Update the ListLayout class for minVisibleTiles if these change.
|
||||
height: 24px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
// We create a flexbox to cheat at alignment
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomSublist2_showNButtonChevron {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 12px;
|
||||
margin-right: 18px;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $roomtile2-preview-color;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showMoreButtonChevron {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showLessButtonChevron {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_hasMenuOpen,
|
||||
&:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:focus-within,
|
||||
&:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover {
|
||||
|
@ -314,6 +318,7 @@ limitations under the License.
|
|||
|
||||
.mx_RoomSublist2_resizeBox {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showNButton {
|
||||
flex-direction: column;
|
||||
|
@ -322,7 +327,6 @@ limitations under the License.
|
|||
margin-right: 12px; // to center
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_menuButton {
|
||||
height: 16px;
|
||||
|
|
3
src/@types/global.d.ts
vendored
3
src/@types/global.d.ts
vendored
|
@ -37,6 +37,9 @@ declare global {
|
|||
mx_RoomListStore2: RoomListStore2;
|
||||
mx_RoomListLayoutStore: RoomListLayoutStore;
|
||||
mxPlatformPeg: PlatformPeg;
|
||||
|
||||
// TODO: Remove flag before launch: https://github.com/vector-im/riot-web/issues/14231
|
||||
mx_QuietRoomListLogging: boolean;
|
||||
}
|
||||
|
||||
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
||||
|
|
|
@ -219,7 +219,10 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
|||
|
||||
private updateLists = () => {
|
||||
const newLists = RoomListStore.instance.orderedLists;
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log("new lists", newLists);
|
||||
}
|
||||
|
||||
this.setState({sublists: newLists}, () => {
|
||||
this.props.onResize();
|
||||
|
|
|
@ -59,7 +59,7 @@ import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
|
|||
* warning disappears. *
|
||||
*******************************************************************/
|
||||
|
||||
const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS
|
||||
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
|
||||
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
|
||||
export const HEADER_HEIGHT = 32; // As defined by CSS
|
||||
|
||||
|
@ -87,6 +87,12 @@ interface IProps {
|
|||
// TODO: Account for https://github.com/vector-im/riot-web/issues/14179
|
||||
}
|
||||
|
||||
// TODO: Use re-resizer's NumberSize when it is exposed as the type
|
||||
interface ResizeDelta {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;
|
||||
|
||||
interface IState {
|
||||
|
@ -94,6 +100,7 @@ interface IState {
|
|||
contextMenuPosition: PartialDOMRect;
|
||||
isResizing: boolean;
|
||||
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
|
||||
height: number;
|
||||
}
|
||||
|
||||
export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||
|
@ -101,28 +108,54 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
private sublistRef = createRef<HTMLDivElement>();
|
||||
private dispatcherRef: string;
|
||||
private layout: ListLayout;
|
||||
private heightAtStart: number;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.layout = RoomListLayoutStore.instance.getLayoutFor(this.props.tagId);
|
||||
|
||||
this.heightAtStart = 0;
|
||||
const height = this.calculateInitialHeight();
|
||||
this.state = {
|
||||
notificationState: RoomNotificationStateStore.instance.getListState(this.props.tagId),
|
||||
contextMenuPosition: null,
|
||||
isResizing: false,
|
||||
isExpanded: this.props.isFiltered ? this.props.isFiltered : !this.layout.isCollapsed
|
||||
isExpanded: this.props.isFiltered ? this.props.isFiltered : !this.layout.isCollapsed,
|
||||
height,
|
||||
};
|
||||
this.state.notificationState.setRooms(this.props.rooms);
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
}
|
||||
|
||||
private calculateInitialHeight() {
|
||||
const requestedVisibleTiles = Math.max(Math.floor(this.layout.visibleTiles), this.layout.minVisibleTiles);
|
||||
const tileCount = Math.min(this.numTiles, requestedVisibleTiles);
|
||||
const height = this.layout.tilesToPixelsWithPadding(tileCount, this.padding);
|
||||
return height;
|
||||
}
|
||||
|
||||
private get padding() {
|
||||
let padding = RESIZE_HANDLE_HEIGHT;
|
||||
// this is used for calculating the max height of the whole container,
|
||||
// and takes into account whether there should be room reserved for the show less button
|
||||
// when fully expanded. Note that the show more button might still be shown when not fully expanded,
|
||||
// but in this case it will take the space of a tile and we don't need to reserve space for it.
|
||||
if (this.numTiles > this.layout.defaultVisibleTiles) {
|
||||
padding += SHOW_N_BUTTON_HEIGHT;
|
||||
}
|
||||
return padding;
|
||||
}
|
||||
|
||||
private get numTiles(): number {
|
||||
return (this.props.rooms || []).length + (this.props.extraBadTilesThatShouldntExist || []).length;
|
||||
return RoomSublist2.calcNumTiles(this.props);
|
||||
}
|
||||
|
||||
private static calcNumTiles(props) {
|
||||
return (props.rooms || []).length + (props.extraBadTilesThatShouldntExist || []).length;
|
||||
}
|
||||
|
||||
private get numVisibleTiles(): number {
|
||||
const nVisible = Math.floor(this.layout.visibleTiles);
|
||||
const nVisible = Math.ceil(this.layout.visibleTiles);
|
||||
return Math.min(nVisible, this.numTiles);
|
||||
}
|
||||
|
||||
|
@ -135,6 +168,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
this.setState({isExpanded: !this.layout.isCollapsed});
|
||||
}
|
||||
}
|
||||
// as the rooms can come in one by one we need to reevaluate
|
||||
// the amount of available rooms to cap the amount of requested visible rooms by the layout
|
||||
if (RoomSublist2.calcNumTiles(prevProps) !== this.numTiles) {
|
||||
this.setState({height: this.calculateInitialHeight()});
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
@ -166,47 +204,50 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
if (this.props.onAddRoom) this.props.onAddRoom();
|
||||
};
|
||||
|
||||
private applyHeightChange(newHeight: number) {
|
||||
const heightInTiles = Math.ceil(this.layout.pixelsToTiles(newHeight - this.padding));
|
||||
this.layout.visibleTiles = Math.min(this.numTiles, heightInTiles);
|
||||
}
|
||||
|
||||
private onResize = (
|
||||
e: MouseEvent | TouchEvent,
|
||||
travelDirection: Direction,
|
||||
refToElement: HTMLDivElement,
|
||||
delta: { width: number, height: number }, // TODO: Use re-resizer's NumberSize when it is exposed as the type
|
||||
delta: ResizeDelta,
|
||||
) => {
|
||||
// Do some sanity checks, but in reality we shouldn't need these.
|
||||
if (travelDirection !== "bottom") return;
|
||||
if (delta.height === 0) return; // something went wrong, so just ignore it.
|
||||
|
||||
// NOTE: the movement in the MouseEvent (not present on a TouchEvent) is inaccurate
|
||||
// for our purposes. The delta provided by the library is also a change *from when
|
||||
// resizing started*, meaning it is fairly useless for us. This is why we just use
|
||||
// the client height and run with it.
|
||||
|
||||
const heightBefore = this.layout.visibleTiles;
|
||||
const heightInTiles = this.layout.pixelsToTiles(refToElement.clientHeight);
|
||||
this.layout.setVisibleTilesWithin(heightInTiles, this.numTiles);
|
||||
if (heightBefore === this.layout.visibleTiles) return; // no-op
|
||||
this.forceUpdate(); // because the layout doesn't trigger a re-render
|
||||
const newHeight = this.heightAtStart + delta.height;
|
||||
this.applyHeightChange(newHeight);
|
||||
this.setState({height: newHeight});
|
||||
};
|
||||
|
||||
private onResizeStart = () => {
|
||||
this.heightAtStart = this.state.height;
|
||||
this.setState({isResizing: true});
|
||||
};
|
||||
|
||||
private onResizeStop = () => {
|
||||
this.setState({isResizing: false});
|
||||
private onResizeStop = (
|
||||
e: MouseEvent | TouchEvent,
|
||||
travelDirection: Direction,
|
||||
refToElement: HTMLDivElement,
|
||||
delta: ResizeDelta,
|
||||
) => {
|
||||
const newHeight = this.heightAtStart + delta.height;
|
||||
this.applyHeightChange(newHeight);
|
||||
this.setState({isResizing: false, height: newHeight});
|
||||
};
|
||||
|
||||
private onShowAllClick = () => {
|
||||
const numVisibleTiles = this.numVisibleTiles;
|
||||
this.layout.visibleTiles = this.layout.tilesWithPadding(this.numTiles, MAX_PADDING_HEIGHT);
|
||||
this.forceUpdate(); // because the layout doesn't trigger a re-render
|
||||
setImmediate(this.focusRoomTile, numVisibleTiles); // focus the tile after the current bottom one
|
||||
const newHeight = this.layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
|
||||
this.applyHeightChange(newHeight);
|
||||
this.setState({height: newHeight}, () => {
|
||||
this.focusRoomTile(this.numTiles - 1);
|
||||
});
|
||||
};
|
||||
|
||||
private onShowLessClick = () => {
|
||||
this.layout.visibleTiles = this.layout.defaultVisibleTiles;
|
||||
this.forceUpdate(); // because the layout doesn't trigger a re-render
|
||||
// focus will flow to the show more button here
|
||||
const newHeight = this.layout.tilesToPixelsWithPadding(this.layout.defaultVisibleTiles, this.padding);
|
||||
this.applyHeightChange(newHeight);
|
||||
this.setState({height: newHeight});
|
||||
};
|
||||
|
||||
private focusRoomTile = (index: number) => {
|
||||
|
@ -559,7 +600,6 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
// TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185
|
||||
|
||||
const visibleTiles = this.renderVisibleTiles();
|
||||
|
||||
const classes = classNames({
|
||||
'mx_RoomSublist2': true,
|
||||
'mx_RoomSublist2_hasMenuOpen': !!this.state.contextMenuPosition,
|
||||
|
@ -570,6 +610,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
if (visibleTiles.length > 0) {
|
||||
const layout = this.layout; // to shorten calls
|
||||
|
||||
const minTiles = Math.min(layout.minVisibleTiles, this.numTiles);
|
||||
const showMoreAtMinHeight = minTiles < this.numTiles;
|
||||
const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0);
|
||||
const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding);
|
||||
const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
|
||||
const showMoreBtnClasses = classNames({
|
||||
'mx_RoomSublist2_showNButton': true,
|
||||
});
|
||||
|
@ -578,9 +623,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
// floats above the resize handle, if we have one present. If the user has all
|
||||
// tiles visible, it becomes 'show less'.
|
||||
let showNButton = null;
|
||||
if (this.numTiles > visibleTiles.length) {
|
||||
// we have a cutoff condition - add the button to show all
|
||||
const numMissing = this.numTiles - visibleTiles.length;
|
||||
|
||||
if (maxTilesPx > this.state.height) {
|
||||
const nonPaddedHeight = this.state.height - RESIZE_HANDLE_HEIGHT - SHOW_N_BUTTON_HEIGHT;
|
||||
const amountFullyShown = Math.floor(nonPaddedHeight / this.layout.tileHeight);
|
||||
const numMissing = this.numTiles - amountFullyShown;
|
||||
let showMoreText = (
|
||||
<span className='mx_RoomSublist2_showNButtonText'>
|
||||
{_t("Show %(count)s more", {count: numMissing})}
|
||||
|
@ -595,7 +642,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
{showMoreText}
|
||||
</RovingAccessibleButton>
|
||||
);
|
||||
} else if (this.numTiles <= visibleTiles.length && this.numTiles > this.layout.defaultVisibleTiles) {
|
||||
} else if (this.numTiles > this.layout.defaultVisibleTiles) {
|
||||
// we have all tiles visible - add a button to show less
|
||||
let showLessText = (
|
||||
<span className='mx_RoomSublist2_showNButtonText'>
|
||||
|
@ -639,44 +686,31 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
// goes backwards and can become wildly incorrect (visibleTiles says 18 when there's
|
||||
// only mathematically 7 possible).
|
||||
|
||||
// The padding is variable though, so figure out what we need padding for.
|
||||
let padding = 0;
|
||||
if (showNButton) padding += SHOW_N_BUTTON_HEIGHT;
|
||||
padding += RESIZE_HANDLE_HEIGHT; // always append the handle height
|
||||
const handleWrapperClasses = classNames({
|
||||
'mx_RoomSublist2_resizerHandles': true,
|
||||
'mx_RoomSublist2_resizerHandles_showNButton': !!showNButton,
|
||||
});
|
||||
|
||||
const relativeTiles = layout.tilesWithPadding(this.numTiles, padding);
|
||||
const minTilesPx = layout.calculateTilesToPixelsMin(relativeTiles, layout.minVisibleTiles, padding);
|
||||
const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, padding);
|
||||
const tilesWithoutPadding = Math.min(relativeTiles, layout.visibleTiles);
|
||||
const tilesPx = layout.calculateTilesToPixelsMin(relativeTiles, tilesWithoutPadding, padding);
|
||||
|
||||
// Now that we know our padding constraints, let's find out if we need to chop off the
|
||||
// last rendered visible tile so it doesn't collide with the 'show more' button
|
||||
let visibleUnpaddedTiles = Math.round(layout.visibleTiles - layout.pixelsToTiles(padding));
|
||||
if (visibleUnpaddedTiles === visibleTiles.length - 1) {
|
||||
const placeholder = <div className="mx_RoomSublist2_placeholder" key='placeholder' />;
|
||||
visibleTiles.splice(visibleUnpaddedTiles, 1, placeholder);
|
||||
}
|
||||
|
||||
const dimensions = {
|
||||
height: tilesPx,
|
||||
};
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<Resizable
|
||||
size={dimensions as any}
|
||||
size={{height: this.state.height} as any}
|
||||
minHeight={minTilesPx}
|
||||
maxHeight={maxTilesPx}
|
||||
onResizeStart={this.onResizeStart}
|
||||
onResizeStop={this.onResizeStop}
|
||||
onResize={this.onResize}
|
||||
handleWrapperClass="mx_RoomSublist2_resizerHandles"
|
||||
handleWrapperClass={handleWrapperClasses}
|
||||
handleClasses={{bottom: "mx_RoomSublist2_resizerHandle"}}
|
||||
className="mx_RoomSublist2_resizeBox"
|
||||
enable={handles}
|
||||
>
|
||||
<div className="mx_RoomSublist2_tiles">
|
||||
{visibleTiles}
|
||||
</div>
|
||||
{showNButton}
|
||||
</Resizable>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -256,7 +256,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
|||
0
|
||||
));
|
||||
} else {
|
||||
console.log(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
|
||||
console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
|
||||
}
|
||||
|
||||
if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
||||
|
|
|
@ -20,7 +20,8 @@ const TILE_HEIGHT_PX = 44;
|
|||
|
||||
// this comes from the CSS where the show more button is
|
||||
// mathematically this percent of a tile when floating.
|
||||
const RESIZER_BOX_FACTOR = 0.78;
|
||||
//const RESIZER_BOX_FACTOR = 0.78;
|
||||
const RESIZER_BOX_FACTOR = 0;
|
||||
|
||||
interface ISerializedListLayout {
|
||||
numTiles: number;
|
||||
|
@ -109,6 +110,10 @@ export class ListLayout {
|
|||
return this.tilesToPixels(Math.min(maxTiles, n)) + padding;
|
||||
}
|
||||
|
||||
public tilesWithResizerBoxFactor(n: number): number {
|
||||
return n + RESIZER_BOX_FACTOR;
|
||||
}
|
||||
|
||||
public tilesWithPadding(n: number, paddingPx: number): number {
|
||||
return this.pixelsToTiles(this.tilesToPixelsWithPadding(n, paddingPx));
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import { EffectiveMembership, getEffectiveMembership } from "./membership";
|
|||
import { ListLayout } from "./ListLayout";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import RoomListLayoutStore from "./RoomListLayoutStore";
|
||||
import { MarkedExecution } from "../../utils/MarkedExecution";
|
||||
|
||||
interface IState {
|
||||
tagsEnabled?: boolean;
|
||||
|
@ -51,7 +52,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
private algorithm = new Algorithm();
|
||||
private filterConditions: IFilterCondition[] = [];
|
||||
private tagWatcher = new TagWatcher(this);
|
||||
private layoutMap: Map<TagID, ListLayout> = new Map<TagID, ListLayout>();
|
||||
private updateFn = new MarkedExecution(() => this.emit(LISTS_UPDATE_EVENT));
|
||||
|
||||
private readonly watchedSettings = [
|
||||
'feature_custom_tags',
|
||||
|
@ -62,7 +63,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
|
||||
this.checkEnabled();
|
||||
for (const settingName of this.watchedSettings) SettingsStore.monitorSetting(settingName, null);
|
||||
RoomViewStore.addListener(this.onRVSUpdate);
|
||||
RoomViewStore.addListener(() => this.handleRVSUpdate({}));
|
||||
this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
|
||||
}
|
||||
|
||||
|
@ -91,27 +92,42 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
await this.updateAlgorithmInstances();
|
||||
}
|
||||
|
||||
private onRVSUpdate = () => {
|
||||
/**
|
||||
* Handles suspected RoomViewStore changes.
|
||||
* @param trigger Set to false to prevent a list update from being sent. Should only
|
||||
* be used if the calling code will manually trigger the update.
|
||||
*/
|
||||
private async handleRVSUpdate({trigger = true}) {
|
||||
if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231
|
||||
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
|
||||
|
||||
const activeRoomId = RoomViewStore.getRoomId();
|
||||
if (!activeRoomId && this.algorithm.stickyRoom) {
|
||||
this.algorithm.stickyRoom = null;
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
} else if (activeRoomId) {
|
||||
const activeRoom = this.matrixClient.getRoom(activeRoomId);
|
||||
if (!activeRoom) {
|
||||
console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`);
|
||||
this.algorithm.stickyRoom = null;
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
} else if (activeRoom !== this.algorithm.stickyRoom) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Changing sticky room to ${activeRoomId}`);
|
||||
this.algorithm.stickyRoom = activeRoom;
|
||||
}
|
||||
await this.algorithm.setStickyRoom(activeRoom);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected async onDispatch(payload: ActionPayload) {
|
||||
if (trigger) this.updateFn.trigger();
|
||||
}
|
||||
|
||||
protected onDispatch(payload: ActionPayload) {
|
||||
// We do this to intentionally break out of the current event loop task, allowing
|
||||
// us to instead wait for a more convenient time to run our updates.
|
||||
setImmediate(() => this.onDispatchAsync(payload));
|
||||
}
|
||||
|
||||
protected async onDispatchAsync(payload: ActionPayload) {
|
||||
if (payload.action === 'MatrixActions.sync') {
|
||||
// Filter out anything that isn't the first PREPARED sync.
|
||||
if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) {
|
||||
|
@ -127,8 +143,12 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
// Update any settings here, as some may have happened before we were logically ready.
|
||||
console.log("Regenerating room lists: Startup");
|
||||
await this.readAndCacheSettingsFromStore();
|
||||
await this.regenerateAllLists();
|
||||
this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed
|
||||
await this.regenerateAllLists({trigger: false});
|
||||
await this.handleRVSUpdate({trigger: false}); // fake an RVS update to adjust sticky room, if needed
|
||||
|
||||
this.updateFn.trigger();
|
||||
|
||||
return; // no point in running the next conditions - they won't match
|
||||
}
|
||||
|
||||
// TODO: Remove this once the RoomListStore becomes default
|
||||
|
@ -137,7 +157,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') {
|
||||
// Reset state without causing updates as the client will have been destroyed
|
||||
// and downstream code will throw NPE errors.
|
||||
this.reset(null, true);
|
||||
await this.reset(null, true);
|
||||
this._matrixClient = null;
|
||||
this.initialListsGenerated = false; // we'll want to regenerate them
|
||||
}
|
||||
|
@ -151,7 +171,8 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
console.log("Regenerating room lists: Settings changed");
|
||||
await this.readAndCacheSettingsFromStore();
|
||||
|
||||
await this.regenerateAllLists(); // regenerate the lists now
|
||||
await this.regenerateAllLists({trigger: false}); // regenerate the lists now
|
||||
this.updateFn.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,16 +190,22 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
console.warn(`Own read receipt was in unknown room ${room.roomId}`);
|
||||
return;
|
||||
}
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`);
|
||||
}
|
||||
await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt);
|
||||
this.updateFn.trigger();
|
||||
return;
|
||||
}
|
||||
} else if (payload.action === 'MatrixActions.Room.tags') {
|
||||
const roomPayload = (<any>payload); // TODO: Type out the dispatcher types
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`);
|
||||
}
|
||||
await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange);
|
||||
this.updateFn.trigger();
|
||||
} else if (payload.action === 'MatrixActions.Room.timeline') {
|
||||
const eventPayload = (<any>payload); // TODO: Type out the dispatcher types
|
||||
|
||||
|
@ -188,12 +215,16 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
const roomId = eventPayload.event.getRoomId();
|
||||
const room = this.matrixClient.getRoom(roomId);
|
||||
const tryUpdate = async (updatedRoom: Room) => {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` +
|
||||
` in ${updatedRoom.roomId}`);
|
||||
}
|
||||
if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`);
|
||||
}
|
||||
const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']);
|
||||
if (newRoom) {
|
||||
// If we have the new room, then the new room check will have seen the predecessor
|
||||
|
@ -202,6 +233,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
}
|
||||
}
|
||||
await this.handleRoomUpdate(updatedRoom, RoomUpdateCause.Timeline);
|
||||
this.updateFn.trigger();
|
||||
};
|
||||
if (!room) {
|
||||
console.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`);
|
||||
|
@ -222,13 +254,18 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`);
|
||||
return;
|
||||
}
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`);
|
||||
}
|
||||
await this.handleRoomUpdate(room, RoomUpdateCause.Timeline);
|
||||
this.updateFn.trigger();
|
||||
} else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') {
|
||||
const eventPayload = (<any>payload); // TODO: Type out the dispatcher types
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Received updated DM map`);
|
||||
}
|
||||
const dmMap = eventPayload.event.getContent();
|
||||
for (const userId of Object.keys(dmMap)) {
|
||||
const roomIds = dmMap[userId];
|
||||
|
@ -246,51 +283,73 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
await this.handleRoomUpdate(room, RoomUpdateCause.PossibleTagChange);
|
||||
}
|
||||
}
|
||||
this.updateFn.trigger();
|
||||
} else if (payload.action === 'MatrixActions.Room.myMembership') {
|
||||
const membershipPayload = (<any>payload); // TODO: Type out the dispatcher types
|
||||
const oldMembership = getEffectiveMembership(membershipPayload.oldMembership);
|
||||
const newMembership = getEffectiveMembership(membershipPayload.membership);
|
||||
if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`);
|
||||
}
|
||||
|
||||
// If we're joining an upgraded room, we'll want to make sure we don't proliferate
|
||||
// the dead room in the list.
|
||||
const createEvent = membershipPayload.room.currentState.getStateEvents("m.room.create", "");
|
||||
if (createEvent && createEvent.getContent()['predecessor']) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Room has a predecessor`);
|
||||
}
|
||||
const prevRoom = this.matrixClient.getRoom(createEvent.getContent()['predecessor']['room_id']);
|
||||
if (prevRoom) {
|
||||
const isSticky = this.algorithm.stickyRoom === prevRoom;
|
||||
if (isSticky) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
|
||||
await this.algorithm.setStickyRoomAsync(null);
|
||||
}
|
||||
await this.algorithm.setStickyRoom(null);
|
||||
}
|
||||
|
||||
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
|
||||
// avoid redundant updates.
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Removing previous room from room list`);
|
||||
}
|
||||
await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Adding new room to room list`);
|
||||
}
|
||||
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
|
||||
this.updateFn.trigger();
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`);
|
||||
}
|
||||
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom);
|
||||
this.updateFn.trigger();
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not a join, it's transitioning into a different list (possibly historical)
|
||||
if (oldMembership !== newMembership) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`);
|
||||
}
|
||||
await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange);
|
||||
this.updateFn.trigger();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -299,9 +358,11 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
|
||||
const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
|
||||
if (shouldUpdate) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`);
|
||||
this.emit(LISTS_UPDATE_EVENT, this);
|
||||
}
|
||||
this.updateFn.mark();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,6 +370,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
await this.algorithm.setTagSorting(tagId, sort);
|
||||
// TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114
|
||||
localStorage.setItem(`mx_tagSort_${tagId}`, sort);
|
||||
this.updateFn.triggerIfWillMark();
|
||||
}
|
||||
|
||||
public getTagSorting(tagId: TagID): SortAlgorithm {
|
||||
|
@ -347,6 +409,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
await this.algorithm.setListOrdering(tagId, order);
|
||||
// TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114
|
||||
localStorage.setItem(`mx_listOrder_${tagId}`, order);
|
||||
this.updateFn.triggerIfWillMark();
|
||||
}
|
||||
|
||||
public getListOrder(tagId: TagID): ListAlgorithm {
|
||||
|
@ -382,6 +445,10 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
}
|
||||
|
||||
private async updateAlgorithmInstances() {
|
||||
// We'll require an update, so mark for one. Marking now also prevents the calls
|
||||
// to setTagSorting and setListOrder from causing triggers.
|
||||
this.updateFn.mark();
|
||||
|
||||
for (const tag of Object.keys(this.orderedLists)) {
|
||||
const definedSort = this.getTagSorting(tag);
|
||||
const definedOrder = this.getListOrder(tag);
|
||||
|
@ -405,12 +472,19 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
}
|
||||
|
||||
private onAlgorithmListUpdated = () => {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log("Underlying algorithm has triggered a list update - refiring");
|
||||
this.emit(LISTS_UPDATE_EVENT, this);
|
||||
console.log("Underlying algorithm has triggered a list update - marking");
|
||||
}
|
||||
this.updateFn.mark();
|
||||
};
|
||||
|
||||
private async regenerateAllLists() {
|
||||
/**
|
||||
* Regenerates the room whole room list, discarding any previous results.
|
||||
* @param trigger Set to false to prevent a list update from being sent. Should only
|
||||
* be used if the calling code will manually trigger the update.
|
||||
*/
|
||||
private async regenerateAllLists({trigger = true}) {
|
||||
console.warn("Regenerating all room lists");
|
||||
|
||||
const sorts: ITagSortingMap = {};
|
||||
|
@ -435,21 +509,26 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
|
||||
this.initialListsGenerated = true;
|
||||
|
||||
this.emit(LISTS_UPDATE_EVENT, this);
|
||||
if (trigger) this.updateFn.trigger();
|
||||
}
|
||||
|
||||
public addFilter(filter: IFilterCondition): void {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log("Adding filter condition:", filter);
|
||||
}
|
||||
this.filterConditions.push(filter);
|
||||
if (this.algorithm) {
|
||||
this.algorithm.addFilterCondition(filter);
|
||||
}
|
||||
this.updateFn.trigger();
|
||||
}
|
||||
|
||||
public removeFilter(filter: IFilterCondition): void {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log("Removing filter condition:", filter);
|
||||
}
|
||||
const idx = this.filterConditions.indexOf(filter);
|
||||
if (idx >= 0) {
|
||||
this.filterConditions.splice(idx, 1);
|
||||
|
@ -458,6 +537,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
|
|||
this.algorithm.removeFilterCondition(filter);
|
||||
}
|
||||
}
|
||||
this.updateFn.trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,12 +87,6 @@ export class Algorithm extends EventEmitter {
|
|||
return this._stickyRoom ? this._stickyRoom.room : null;
|
||||
}
|
||||
|
||||
public set stickyRoom(val: Room) {
|
||||
// setters can't be async, so we call a private function to do the work
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.updateStickyRoom(val);
|
||||
}
|
||||
|
||||
protected get hasFilters(): boolean {
|
||||
return this.allowedByFilter.size > 0;
|
||||
}
|
||||
|
@ -115,7 +109,7 @@ export class Algorithm extends EventEmitter {
|
|||
* Awaitable version of the sticky room setter.
|
||||
* @param val The new room to sticky.
|
||||
*/
|
||||
public async setStickyRoomAsync(val: Room) {
|
||||
public async setStickyRoom(val: Room) {
|
||||
await this.updateStickyRoom(val);
|
||||
}
|
||||
|
||||
|
@ -321,9 +315,11 @@ export class Algorithm extends EventEmitter {
|
|||
}
|
||||
newMap[tagId] = allowedRoomsInThisTag;
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`);
|
||||
}
|
||||
}
|
||||
|
||||
const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, <Room[]>[]);
|
||||
this.allowedRoomsByFilters = new Set(allowedRooms);
|
||||
|
@ -331,26 +327,13 @@ export class Algorithm extends EventEmitter {
|
|||
this.emit(LIST_UPDATED_EVENT);
|
||||
}
|
||||
|
||||
// TODO: Remove or use.
|
||||
protected addPossiblyFilteredRoomsToTag(tagId: TagID, added: Room[]): void {
|
||||
const filters = this.allowedByFilter.keys();
|
||||
for (const room of added) {
|
||||
for (const filter of filters) {
|
||||
if (filter.isVisible(room)) {
|
||||
this.allowedRoomsByFilters.add(room);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've updated the allowed rooms, recalculate the tag
|
||||
this.recalculateFilteredRoomsForTag(tagId);
|
||||
}
|
||||
|
||||
protected recalculateFilteredRoomsForTag(tagId: TagID): void {
|
||||
if (!this.hasFilters) return; // don't bother doing work if there's nothing to do
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Recalculating filtered rooms for ${tagId}`);
|
||||
}
|
||||
delete this.filteredRooms[tagId];
|
||||
const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone
|
||||
this.tryInsertStickyRoomToFilterSet(rooms, tagId);
|
||||
|
@ -359,9 +342,11 @@ export class Algorithm extends EventEmitter {
|
|||
this.filteredRooms[tagId] = filteredRooms;
|
||||
}
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) {
|
||||
if (!this._stickyRoom || !this._stickyRoom.room || this._stickyRoom.tag !== tagId) return;
|
||||
|
@ -399,8 +384,10 @@ export class Algorithm extends EventEmitter {
|
|||
}
|
||||
|
||||
if (!this._cachedStickyRooms || !updatedTag) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Generating clone of cached rooms for sticky room handling`);
|
||||
}
|
||||
const stickiedTagMap: ITagMap = {};
|
||||
for (const tagId of Object.keys(this.cachedRooms)) {
|
||||
stickiedTagMap[tagId] = this.cachedRooms[tagId].map(r => r); // shallow clone
|
||||
|
@ -411,8 +398,10 @@ export class Algorithm extends EventEmitter {
|
|||
if (updatedTag) {
|
||||
// Update the tag indicated by the caller, if possible. This is mostly to ensure
|
||||
// our cache is up to date.
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Replacing cached sticky rooms for ${updatedTag}`);
|
||||
}
|
||||
this._cachedStickyRooms[updatedTag] = this.cachedRooms[updatedTag].map(r => r); // shallow clone
|
||||
}
|
||||
|
||||
|
@ -421,8 +410,10 @@ export class Algorithm extends EventEmitter {
|
|||
// we might have updated from the cache is also our sticky room.
|
||||
const sticky = this._stickyRoom;
|
||||
if (!updatedTag || updatedTag === sticky.tag) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Inserting sticky room ${sticky.room.roomId} at position ${sticky.position} in ${sticky.tag}`);
|
||||
}
|
||||
this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room);
|
||||
}
|
||||
|
||||
|
@ -647,8 +638,10 @@ export class Algorithm extends EventEmitter {
|
|||
* processing.
|
||||
*/
|
||||
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
|
||||
}
|
||||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
// Note: check the isSticky against the room ID just in case the reference is wrong
|
||||
|
@ -705,16 +698,20 @@ export class Algorithm extends EventEmitter {
|
|||
const diff = arrayDiff(oldTags, newTags);
|
||||
if (diff.removed.length > 0 || diff.added.length > 0) {
|
||||
for (const rmTag of diff.removed) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Removing ${room.roomId} from ${rmTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
this.cachedRooms[rmTag] = algorithm.orderedRooms;
|
||||
}
|
||||
for (const addTag of diff.added) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Adding ${room.roomId} to ${addTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
|
@ -724,13 +721,17 @@ export class Algorithm extends EventEmitter {
|
|||
// Update the tag map so we don't regen it in a moment
|
||||
this.roomIdsToTags[room.roomId] = newTags;
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
didTagChange = true;
|
||||
} else {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.warn(`Received no-op update for ${room.roomId} - changing to Timeline update`);
|
||||
console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
}
|
||||
|
||||
|
@ -746,7 +747,7 @@ export class Algorithm extends EventEmitter {
|
|||
};
|
||||
} else {
|
||||
// We have to clear the lock as the sticky room change will trigger updates.
|
||||
await this.setStickyRoomAsync(room);
|
||||
await this.setStickyRoom(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -756,20 +757,27 @@ export class Algorithm extends EventEmitter {
|
|||
// as the sticky room relies on this.
|
||||
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
|
||||
if (this.stickyRoom === room) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.roomIdsToTags[room.roomId]) {
|
||||
if (CAUSES_REQUIRING_ROOM.includes(cause)) {
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
|
||||
}
|
||||
|
||||
// Get the tags for the room and populate the cache
|
||||
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
|
||||
|
@ -780,12 +788,16 @@ export class Algorithm extends EventEmitter {
|
|||
|
||||
this.roomIdsToTags[room.roomId] = roomTags;
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
|
||||
}
|
||||
|
||||
const tags = this.roomIdsToTags[room.roomId];
|
||||
if (!tags) {
|
||||
|
@ -807,8 +819,10 @@ export class Algorithm extends EventEmitter {
|
|||
changed = true;
|
||||
}
|
||||
|
||||
if (!window.mx_QuietRoomListLogging) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,9 +87,6 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
|
|||
|
||||
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
|
||||
super(tagId, initialSortingAlgorithm);
|
||||
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Constructed an ImportanceAlgorithm for ${tagId}`);
|
||||
}
|
||||
|
||||
// noinspection JSMethodCanBeStatic
|
||||
|
|
|
@ -28,9 +28,6 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
|
|||
|
||||
public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
|
||||
super(tagId, initialSortingAlgorithm);
|
||||
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log(`[RoomListDebug] Constructed a NaturalAlgorithm for ${tagId}`);
|
||||
}
|
||||
|
||||
public async setRooms(rooms: Room[]): Promise<any> {
|
||||
|
|
|
@ -52,8 +52,6 @@ export class CommunityFilterCondition extends EventEmitter implements IFilterCon
|
|||
const beforeRoomIds = this.roomIds;
|
||||
this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId);
|
||||
if (arrayHasDiff(beforeRoomIds, this.roomIds)) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log("Updating filter for group: ", this.community.groupId);
|
||||
this.emit(FILTER_CHANGED);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -41,8 +41,6 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
|
|||
|
||||
public set search(val: string) {
|
||||
this._search = val;
|
||||
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
|
||||
console.log("Updating filter for room name search:", this._search);
|
||||
this.emit(FILTER_CHANGED);
|
||||
}
|
||||
|
||||
|
|
67
src/utils/MarkedExecution.ts
Normal file
67
src/utils/MarkedExecution.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A utility to ensure that a function is only called once triggered with
|
||||
* a mark applied. Multiple marks can be applied to the function, however
|
||||
* the function will only be called once upon trigger().
|
||||
*
|
||||
* The function starts unmarked.
|
||||
*/
|
||||
export class MarkedExecution {
|
||||
private marked = false;
|
||||
|
||||
/**
|
||||
* Creates a MarkedExecution for the provided function.
|
||||
* @param fn The function to be called upon trigger if marked.
|
||||
*/
|
||||
constructor(private fn: () => void) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the mark without calling the function.
|
||||
*/
|
||||
public reset() {
|
||||
this.marked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the function to be called upon trigger().
|
||||
*/
|
||||
public mark() {
|
||||
this.marked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If marked, the function will be called, otherwise this does nothing.
|
||||
*/
|
||||
public trigger() {
|
||||
if (!this.marked) return;
|
||||
this.reset(); // reset first just in case the fn() causes a trigger()
|
||||
this.fn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the function if a mark() call would mark it. If the function
|
||||
* has already been marked this will do nothing.
|
||||
*/
|
||||
public triggerIfWillMark() {
|
||||
if (!this.marked) {
|
||||
this.mark();
|
||||
this.trigger();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue