Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add memberlist right-panel to Hydrogen #395

Merged
merged 106 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
69237fc
Basic barebones of memberlist view
MidhunSureshR Jun 11, 2021
7e6e4ec
Duplicate navigation from room details
MidhunSureshR Jun 11, 2021
b74e445
Move rightpanel to separate view and vm
MidhunSureshR Jun 17, 2021
7500bbe
Modify navigation to work with rightpanel segment
MidhunSureshR Jun 17, 2021
8b01ca5
Create RightPanel from SessionViewModel
MidhunSureshR Jun 17, 2021
dfe7385
Accommodate "rightpanel" navigation segment
MidhunSureshR Jun 17, 2021
1418645
Modify CSS to reflect changes
MidhunSureshR Jun 17, 2021
46a6cf6
Add memberlist to navigation
MidhunSureshR Jun 17, 2021
f3c7ab3
Remove code from session view/vm
MidhunSureshR Jun 17, 2021
a765d7f
Create memberlist view from rightpanel
MidhunSureshR Jun 17, 2021
abd2c19
Remove unused param
MidhunSureshR Jun 17, 2021
7e72d57
Make list scrollable
MidhunSureshR Jun 17, 2021
ddb7a16
Make member private
MidhunSureshR Jun 18, 2021
f7a6fbd
Make getUserLevel() public
MidhunSureshR Jun 18, 2021
cb5e598
Getter for powerlevel from room
MidhunSureshR Jun 18, 2021
3e23392
Consider powerlevels in comparator and add tests
MidhunSureshR Jun 18, 2021
a9ff6ab
Sort memberlist using powerlevel
MidhunSureshR Jun 18, 2021
a1e3ff3
Use name instead of displayName
MidhunSureshR Jun 18, 2021
2e8c456
Implement set method to support update from value
MidhunSureshR Jun 22, 2021
11eb9c7
Use set instead of add in memberlist
MidhunSureshR Jun 22, 2021
4fcaac3
Add binding in view
MidhunSureshR Jun 22, 2021
b62473a
Add update method
MidhunSureshR Jun 22, 2021
0819dcb
Use MappedList instead of MappedMap
MidhunSureshR Jun 22, 2021
f41c835
Support updater in MappedMap
MidhunSureshR Jun 22, 2021
404129f
Settle on MappedMap
MidhunSureshR Jun 22, 2021
dea0cad
Fix bug in FilteredMap
MidhunSureshR Jun 24, 2021
b8542c6
Implement name disambiguation
MidhunSureshR Jun 25, 2021
bcb46fc
Make tile vm support disambiguation
MidhunSureshR Jun 25, 2021
ef40027
Call disambiguate on update/map
MidhunSureshR Jun 25, 2021
3d4ba20
Put up a temporary loading view
MidhunSureshR Jun 25, 2021
86c1550
Switch to collator for perf reasons
MidhunSureshR Jun 25, 2021
7139711
Add avatar to tile
MidhunSureshR Jun 26, 2021
3fb89a8
Add some initial styling
MidhunSureshR Jun 26, 2021
d64d07a
Move loading view into separate file
MidhunSureshR Jun 27, 2021
bcfd1bd
Support loading view for all panels
MidhunSureshR Jun 27, 2021
e935423
Remove duplication in RightPanelViewModel
MidhunSureshR Jun 27, 2021
3bb82e5
Disambiguator is not async
MidhunSureshR Jun 28, 2021
ab0a48a
rename rightpanel to right-panel
MidhunSureshR Jun 30, 2021
41806b5
Remove duplication
MidhunSureshR Jun 30, 2021
ad6122a
Add explaining comment
MidhunSureshR Jun 30, 2021
da1e981
name changes
MidhunSureshR Jun 30, 2021
ea06d4f
Eliminate double lookup
MidhunSureshR Jul 2, 2021
89e256e
Return array to prevent fetching again
MidhunSureshR Jul 2, 2021
11d411c
Add failing test
MidhunSureshR Jul 2, 2021
53fc6a7
Check prev name is string
MidhunSureshR Jul 2, 2021
db515d4
Inline flatten method
MidhunSureshR Jul 2, 2021
35f6043
Support slice
MidhunSureshR Jul 3, 2021
1d5b163
Export function
MidhunSureshR Jul 3, 2021
0b9f4a5
Add LazyListView
MidhunSureshR Jul 3, 2021
ee07234
Switch to lazy list
MidhunSureshR Jul 3, 2021
452eee6
Incorporate lazyrender code from element
MidhunSureshR Jul 11, 2021
f05574f
Fix updates
MidhunSureshR Jul 12, 2021
96e2bb0
Add explaining comment
MidhunSureshR Jul 12, 2021
9a00143
Improve comment
MidhunSureshR Jul 12, 2021
4cb9adc
Remove misleading comment
MidhunSureshR Jul 12, 2021
85924ab
Fix update method
MidhunSureshR Jul 12, 2021
5338457
Use normalized index in recreateItem
MidhunSureshR Jul 12, 2021
d3a8e95
Find height of container from DOM
MidhunSureshR Jul 12, 2021
ea0851e
Keep memberlist panel open on room/grid change
MidhunSureshR Jul 13, 2021
f506cf6
div --> li
MidhunSureshR Jul 13, 2021
c539c38
Account for padding in itemHeight
MidhunSureshR Jul 13, 2021
d1f465e
Replace slice with iterator
MidhunSureshR Jul 13, 2021
8a976ef
Make powerLevels observable
MidhunSureshR Jul 14, 2021
22fab37
Remove timeline reader and only use roomState
MidhunSureshR Jul 14, 2021
14c00f5
Make loadPowerLevels private
MidhunSureshR Jul 14, 2021
8e39aed
Ensure that power levels are loaded only once
MidhunSureshR Jul 14, 2021
2502c40
Fix broken tests
MidhunSureshR Jul 14, 2021
c073d4c
Unmount child views correctly
MidhunSureshR Jul 14, 2021
fe4f6d2
Remove listHasChangedSize
MidhunSureshR Jul 14, 2021
8ee9cb1
Move css to layout.css
MidhunSureshR Jul 14, 2021
72f79e8
Reduce padding
MidhunSureshR Jul 14, 2021
8e55967
Create UI to open memberlist from details panel
MidhunSureshR Jul 14, 2021
21f47f2
Add chevron to button
MidhunSureshR Jul 14, 2021
f98a884
Implement UX
MidhunSureshR Jul 15, 2021
b126ba1
Fix lazy list css
MidhunSureshR Jul 15, 2021
c7e12c9
Make addPanelIfNeeded more generic
MidhunSureshR Jul 15, 2021
be46d87
Center names
MidhunSureshR Jul 15, 2021
5a54be2
Style loading view
MidhunSureshR Jul 15, 2021
5873ab6
Release memberlist after panel is closed
MidhunSureshR Jul 15, 2021
e406aa6
Add jsdoc for powerlevels
MidhunSureshR Jul 15, 2021
960f2c2
Remove comment
MidhunSureshR Jul 15, 2021
6079379
Subscribe to powerLevels
MidhunSureshR Jul 15, 2021
5c0c590
Move spinner before the text
MidhunSureshR Jul 15, 2021
fe18b61
Css fixes
MidhunSureshR Jul 15, 2021
c4c0e02
Clear margin on ul
MidhunSureshR Jul 15, 2021
4bac98d
More css fixes
MidhunSureshR Jul 15, 2021
9a3d7e4
Remove top padding of room details view
MidhunSureshR Jul 15, 2021
0ac3d37
Remove TemplateView
MidhunSureshR Jul 15, 2021
da733f9
Move files to members directory
MidhunSureshR Jul 15, 2021
1f67aa3
Move LoadingView.js
MidhunSureshR Jul 15, 2021
694b627
Inline method
MidhunSureshR Jul 15, 2021
4946683
Use ifView instead of mapView
MidhunSureshR Jul 15, 2021
c410aed
Use flex instead of setting height
MidhunSureshR Jul 15, 2021
ec8b6f9
Inline method
MidhunSureshR Jul 15, 2021
611524c
Log instead of throwing error
MidhunSureshR Jul 15, 2021
0e0976c
Inline method
MidhunSureshR Jul 15, 2021
829830c
Fix lazylist
MidhunSureshR Jul 16, 2021
0bd1d2b
Improve code
MidhunSureshR Jul 16, 2021
f366479
Fix move bug
MidhunSureshR Jul 16, 2021
9fdfebf
Replace get with iterator and remove lambda
MidhunSureshR Jul 16, 2021
66d5f4d
Make code clearer
MidhunSureshR Jul 16, 2021
ec4a783
Fix disambiguator
MidhunSureshR Jul 16, 2021
436e875
Initialize prop in constructor
MidhunSureshR Jul 16, 2021
86bb56a
Fix layout of details panel in mobile
MidhunSureshR Jul 16, 2021
7c9755d
Fix width for smaller screens
MidhunSureshR Jul 16, 2021
1a721fe
Import from AvatarView.js
MidhunSureshR Jul 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 45 additions & 4 deletions src/domain/navigation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ function allowsChild(parent, child) {
// downside of the approach: both of these will control which tile is selected
return type === "room" || type === "empty-grid-tile";
case "room":
return type === "lightbox" || type === "details";
return type === "lightbox" || type === "right-panel";
case "right-panel":
return type === "details"|| type === "members";
default:
return false;
}
Expand Down Expand Up @@ -85,6 +87,23 @@ function roomsSegmentWithRoom(rooms, roomId, path) {
}
}

function pushRightPanelSegment(array, segment) {
array.push(new Segment("right-panel"));
array.push(new Segment(segment));
}

export function addPanelIfNeeded(navigation, path) {
const segments = navigation.path.segments;
const i = segments.findIndex(segment => segment.type === "right-panel");
let _path = path;
if (i !== -1) {
_path = path.until("room");
_path = _path.with(segments[i]);
_path = _path.with(segments[i + 1]);
}
return _path;
}

export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
// substr(1) to take of initial /
const parts = urlPath.substr(1).split("/");
Expand Down Expand Up @@ -114,7 +133,9 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
}
segments.push(new Segment("room", roomId));
if (currentNavPath.get("details")?.value) {
segments.push(new Segment("details"));
pushRightPanelSegment(segments, "details");
} else if (currentNavPath.get("members")?.value) {
pushRightPanelSegment(segments, "members");
}
} else if (type === "last-session") {
let sessionSegment = currentNavPath.get("session");
Expand All @@ -124,6 +145,8 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
if (sessionSegment) {
segments.push(sessionSegment);
}
} else if (type === "details" || type === "members") {
pushRightPanelSegment(segments, type);
} else {
// might be undefined, which will be turned into true by Segment
const value = iterator.next().value;
Expand Down Expand Up @@ -152,6 +175,9 @@ export function stringifyPath(path) {
urlPath += `/${segment.type}/${segment.value}`;
}
break;
case "right-panel":
// Ignore right-panel in url
continue;
default:
urlPath += `/${segment.type}`;
if (segment.value && segment.value !== true) {
Expand Down Expand Up @@ -185,6 +211,18 @@ export function tests() {
const urlPath = stringifyPath(path);
assert.equal(urlPath, "/session/1/rooms/a,b,c/1");
},
"stringify url with right-panel and details segment": assert => {
const nav = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
new Segment("room", "b"),
new Segment("right-panel"),
new Segment("details")
]);
const urlPath = stringifyPath(path);
assert.equal(urlPath, "/session/1/rooms/a,b,c/1/details");
},
"parse grid url path with focused empty tile": assert => {
const segments = parseUrlPath("/session/1/rooms/a,b,c/3");
assert.equal(segments.length, 3);
Expand Down Expand Up @@ -263,18 +301,21 @@ export function tests() {
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
new Segment("room", "b"),
new Segment("right-panel", true),
new Segment("details", true)
]);
const segments = parseUrlPath("/session/1/open-room/a", path);
assert.equal(segments.length, 4);
assert.equal(segments.length, 5);
assert.equal(segments[0].type, "session");
assert.equal(segments[0].value, "1");
assert.equal(segments[1].type, "rooms");
assert.deepEqual(segments[1].value, ["a", "b", "c"]);
assert.equal(segments[2].type, "room");
assert.equal(segments[2].value, "a");
assert.equal(segments[3].type, "details");
assert.equal(segments[3].type, "right-panel");
assert.equal(segments[3].value, true);
assert.equal(segments[4].type, "details");
assert.equal(segments[4].value, true);
},
"parse open-room action setting a room in an empty tile": assert => {
const nav = new Navigation(allowsChild);
Expand Down
6 changes: 2 additions & 4 deletions src/domain/session/RoomGridViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import {ViewModel} from "../ViewModel.js";
import {addPanelIfNeeded} from "../navigation/index.js";

function dedupeSparse(roomIds) {
return roomIds.map((id, idx) => {
Expand Down Expand Up @@ -79,12 +80,9 @@ export class RoomGridViewModel extends ViewModel {
}

_switchToRoom(roomId) {
const detailsShown = !!this.navigation.path.get("details")?.value;
let path = this.navigation.path.until("rooms");
path = path.with(this.navigation.segment("room", roomId));
if (detailsShown) {
path = path.with(this.navigation.segment("details", true));
}
path = addPanelIfNeeded(this.navigation, path);
this.navigation.applyPath(path);
}

Expand Down
27 changes: 14 additions & 13 deletions src/domain/session/SessionViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.

import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
import {RoomViewModel} from "./room/RoomViewModel.js";
import {RoomDetailsViewModel} from "./rightpanel/RoomDetailsViewModel.js";
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
import {InviteViewModel} from "./room/InviteViewModel.js";
import {LightboxViewModel} from "./room/LightboxViewModel.js";
Expand All @@ -26,6 +25,7 @@ import {RoomGridViewModel} from "./RoomGridViewModel.js";
import {SettingsViewModel} from "./settings/SettingsViewModel.js";
import {ViewModel} from "../ViewModel.js";
import {RoomViewModelObservable} from "./RoomViewModelObservable.js";
import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js";

export class SessionViewModel extends ViewModel {
constructor(options) {
Expand Down Expand Up @@ -63,7 +63,7 @@ export class SessionViewModel extends ViewModel {
if (!this._gridViewModel) {
this._updateRoom(roomId);
}
this._updateRoomDetails();
this._updateRightPanel();
}));
if (!this._gridViewModel) {
this._updateRoom(currentRoomId.get());
Expand All @@ -81,9 +81,10 @@ export class SessionViewModel extends ViewModel {
}));
this._updateLightbox(lightbox.get());

const details = this.navigation.observe("details");
this.track(details.subscribe(() => this._updateRoomDetails()));
this._updateRoomDetails();

const rightpanel = this.navigation.observe("right-panel");
this.track(rightpanel.subscribe(() => this._updateRightPanel()));
this._updateRightPanel();
}

get id() {
Expand Down Expand Up @@ -118,8 +119,9 @@ export class SessionViewModel extends ViewModel {
return this._roomViewModelObservable?.get();
}

get roomDetailsViewModel() {
return this._roomDetailsViewModel;

get rightPanelViewModel() {
return this._rightPanelViewModel;
}

_updateGrid(roomIds) {
Expand Down Expand Up @@ -256,15 +258,14 @@ export class SessionViewModel extends ViewModel {
return room;
}

_updateRoomDetails() {
this._roomDetailsViewModel = this.disposeTracked(this._roomDetailsViewModel);
const enable = !!this.navigation.path.get("details")?.value;
_updateRightPanel() {
this._rightPanelViewModel = this.disposeTracked(this._rightPanelViewModel);
const enable = !!this.navigation.path.get("right-panel")?.value;
if (enable) {
const room = this._roomFromNavigation();
if (!room) { return; }
this._roomDetailsViewModel = this.track(new RoomDetailsViewModel(this.childOptions({room})));
this._rightPanelViewModel = this.track(new RightPanelViewModel(this.childOptions({room})));
}
this.emitChange("roomDetailsViewModel");
this.emitChange("rightPanelViewModel");
}

}
10 changes: 3 additions & 7 deletions src/domain/session/leftpanel/LeftPanelViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {RoomTileViewModel} from "./RoomTileViewModel.js";
import {InviteTileViewModel} from "./InviteTileViewModel.js";
import {RoomFilter} from "./RoomFilter.js";
import {ApplyMap} from "../../../observable/map/ApplyMap.js";
import {addPanelIfNeeded} from "../../navigation/index.js";

export class LeftPanelViewModel extends ViewModel {
constructor(options) {
Expand Down Expand Up @@ -92,24 +93,19 @@ export class LeftPanelViewModel extends ViewModel {
}
}

_pathForDetails(path) {
const details = this.navigation.path.get("details");
return details?.value ? path.with(details) : path;
}

toggleGrid() {
const room = this.navigation.path.get("room");
let path = this.navigation.path.until("session");
if (this.gridEnabled) {
if (room) {
path = path.with(room);
path = this._pathForDetails(path);
path = addPanelIfNeeded(this.navigation, path);
}
} else {
if (room) {
path = path.with(this.navigation.segment("rooms", [room.value]));
path = path.with(room);
path = this._pathForDetails(path);
path = addPanelIfNeeded(this.navigation, path);
} else {
path = path.with(this.navigation.segment("rooms", []));
path = path.with(this.navigation.segment("empty-grid-tile", 0));
Expand Down
42 changes: 42 additions & 0 deletions src/domain/session/rightpanel/MemberListViewModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {ViewModel} from "../../ViewModel.js";
import {MemberTileViewModel} from "./MemberTileViewModel.js";
import {createMemberComparator} from "./members/comparator.js";
import {Disambiguator} from "./members/disambiguator.js";

export class MemberListViewModel extends ViewModel {
constructor(options) {
super(options);
const list = options.members;
this.track(() => list.release());

const powerLevelsObservable = options.powerLevelsObservable;
this.track(powerLevelsObservable.subscribe(() => { /*resort based on new power levels here*/ }));

const powerLevels = powerLevelsObservable.get();
this.memberTileViewModels = this._mapTileViewModels(list.members.filterValues(member => member.membership === "join"))
.sortValues(createMemberComparator(powerLevels));
this.nameDisambiguator = new Disambiguator();
this.mediaRepository = options.mediaRepository;
}

get type() { return "member-list"; }

get shouldShowBackButton() { return true; }

get previousSegmentName() { return "details"; }

_mapTileViewModels(members) {
const mapper = (member, emitChange) => {
const mediaRepository = this.mediaRepository;
const vm = new MemberTileViewModel(this.childOptions({member, emitChange, mediaRepository}));
this.nameDisambiguator.disambiguate(vm);
return vm;
}
const updater = (vm, params, newMember) => {
vm.updateFrom(newMember);
this.nameDisambiguator.disambiguate(vm);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed something here. MappedMap won't run the mapper in onRemove. This will cause any user who leaves and rejoins a room to appear disambiguated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, can you check for this in the mapper when it is run in onAdd?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed for now but there are related issues. For instance if user-A and user-B are disambiguated and user-B leaves the room, we'd ideally want user-A to be un-disambiguated (which we can't really do atm because the disambiguator does not get informed when user-B leaves).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I think this is fine though for now.

};
return members.mapValues(mapper, updater);
}

}
68 changes: 68 additions & 0 deletions src/domain/session/rightpanel/MemberTileViewModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {ViewModel} from "../../ViewModel.js";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";

export class MemberTileViewModel extends ViewModel {
constructor(options) {
super(options);
this._member = this._options.member;
this._mediaRepository = options.mediaRepository
this._previousName = null;
this._nameChanged = true;
}

get name() {
return `${this._member.name}${this._disambiguationPart}`;
}

get _disambiguationPart() {
return this._disambiguate ? ` (${this.userId})` : "";
}

get userId() {
return this._member.userId;
}

get previousName() {
return this._previousName;
}

get nameChanged() {
return this._nameChanged;
}

_updatePreviousName(newName) {
const currentName = this._member.name;
if (currentName !== newName) {
this._previousName = currentName;
this._nameChanged = true;
} else {
this._nameChanged = false;
}
}

setDisambiguation(status) {
this._disambiguate = status;
this.emitChange();
}

updateFrom(newMember) {
this._updatePreviousName(newMember.name);
this._member = newMember;
}

get avatarLetter() {
return avatarInitials(this.name);
}

get avatarColorNumber() {
return getIdentifierColorNumber(this.userId)
}

avatarUrl(size) {
return getAvatarHttpUrl(this._member.avatarUrl, size, this.platform, this._mediaRepository);
}

get avatarTitle() {
return this.name;
}
}