forked from mystiq/hydrogen-web
Merge pull request #417 from MidhunSureshR/member-details
Member Panel - PR 2 - UI
This commit is contained in:
commit
1862e31396
10 changed files with 252 additions and 31 deletions
|
@ -39,7 +39,7 @@ function allowsChild(parent, child) {
|
|||
case "room":
|
||||
return type === "lightbox" || type === "right-panel";
|
||||
case "right-panel":
|
||||
return type === "details"|| type === "members";
|
||||
return type === "details"|| type === "members" || type === "member";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -87,9 +87,9 @@ function roomsSegmentWithRoom(rooms, roomId, path) {
|
|||
}
|
||||
}
|
||||
|
||||
function pushRightPanelSegment(array, segment) {
|
||||
function pushRightPanelSegment(array, segment, value = true) {
|
||||
array.push(new Segment("right-panel"));
|
||||
array.push(new Segment(segment));
|
||||
array.push(new Segment(segment, value));
|
||||
}
|
||||
|
||||
export function addPanelIfNeeded(navigation, path) {
|
||||
|
@ -132,10 +132,11 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
|
|||
segments.push(roomsSegmentWithRoom(rooms, roomId, currentNavPath));
|
||||
}
|
||||
segments.push(new Segment("room", roomId));
|
||||
if (currentNavPath.get("details")?.value) {
|
||||
pushRightPanelSegment(segments, "details");
|
||||
} else if (currentNavPath.get("members")?.value) {
|
||||
pushRightPanelSegment(segments, "members");
|
||||
// Add right-panel segments from previous path
|
||||
const previousSegments = currentNavPath.segments;
|
||||
const i = previousSegments.findIndex(s => s.type === "right-panel");
|
||||
if (i !== -1) {
|
||||
segments.push(...previousSegments.slice(i));
|
||||
}
|
||||
} else if (type === "last-session") {
|
||||
let sessionSegment = currentNavPath.get("session");
|
||||
|
@ -147,6 +148,10 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
|
|||
}
|
||||
} else if (type === "details" || type === "members") {
|
||||
pushRightPanelSegment(segments, type);
|
||||
} else if (type === "member") {
|
||||
const userId = iterator.next().value;
|
||||
if (!userId) { break; }
|
||||
pushRightPanelSegment(segments, type, userId);
|
||||
} else {
|
||||
// might be undefined, which will be turned into true by Segment
|
||||
const value = iterator.next().value;
|
||||
|
|
82
src/domain/session/rightpanel/MemberDetailsViewModel.js
Normal file
82
src/domain/session/rightpanel/MemberDetailsViewModel.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2021 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.
|
||||
*/
|
||||
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
|
||||
|
||||
export class MemberDetailsViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._observableMember = options.observableMember;
|
||||
this._mediaRepository = options.mediaRepository;
|
||||
this._member = this._observableMember.get();
|
||||
this._isEncrypted = options.isEncrypted;
|
||||
this._powerLevelsObservable = options.powerLevelsObservable;
|
||||
this.track(this._powerLevelsObservable.subscribe(() => this._onPowerLevelsChange()));
|
||||
this.track(this._observableMember.subscribe( () => this._onMemberChange()));
|
||||
}
|
||||
|
||||
get name() { return this._member.name; }
|
||||
get userId() { return this._member.userId; }
|
||||
|
||||
get type() { return "member-details"; }
|
||||
get shouldShowBackButton() { return true; }
|
||||
get previousSegmentName() { return "members"; }
|
||||
|
||||
get role() {
|
||||
if (this.powerLevel >= 100) { return this.i18n`Admin`; }
|
||||
else if (this.powerLevel >= 50) { return this.i18n`Moderator`; }
|
||||
else if (this.powerLevel === 0) { return this.i18n`Default`; }
|
||||
else { return this.i18n`Custom (${this.powerLevel})`; }
|
||||
}
|
||||
|
||||
_onMemberChange() {
|
||||
this._member = this._observableMember.get();
|
||||
this.emitChange("member");
|
||||
}
|
||||
|
||||
_onPowerLevelsChange() {
|
||||
this.emitChange("role");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
get isEncrypted() {
|
||||
return this._isEncrypted;
|
||||
}
|
||||
|
||||
get powerLevel() {
|
||||
return this._powerLevelsObservable.get()?.getUserLevel(this._member.userId);
|
||||
}
|
||||
|
||||
get linkToUser() {
|
||||
return `https://matrix.to/#/${this._member.userId}`;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ 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*/ }));
|
||||
|
|
|
@ -46,6 +46,10 @@ export class MemberTileViewModel extends ViewModel {
|
|||
return this._nameChanged;
|
||||
}
|
||||
|
||||
get detailsUrl() {
|
||||
return `${this.urlCreator.urlUntilSegment("room")}/member/${this._member.userId}`;
|
||||
}
|
||||
|
||||
_updatePreviousName(newName) {
|
||||
const currentName = this._member.name;
|
||||
if (currentName !== newName) {
|
||||
|
|
|
@ -17,35 +17,59 @@ limitations under the License.
|
|||
import {ViewModel} from "../../ViewModel.js";
|
||||
import {RoomDetailsViewModel} from "./RoomDetailsViewModel.js";
|
||||
import {MemberListViewModel} from "./MemberListViewModel.js";
|
||||
import {MemberDetailsViewModel} from "./MemberDetailsViewModel.js";
|
||||
|
||||
export class RightPanelViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._room = options.room;
|
||||
this._members = null;
|
||||
this._setupNavigation();
|
||||
}
|
||||
|
||||
get activeViewModel() { return this._activeViewModel; }
|
||||
|
||||
async _getMemberArguments() {
|
||||
const members = await this._room.loadMemberList();
|
||||
async _getMemberListArguments() {
|
||||
if (!this._members) {
|
||||
this._members = await this._room.loadMemberList();
|
||||
this.track(() => this._members.release());
|
||||
}
|
||||
const room = this._room;
|
||||
const powerLevelsObservable = await this._room.observePowerLevels();
|
||||
return {members, powerLevelsObservable, mediaRepository: room.mediaRepository};
|
||||
return {members: this._members, powerLevelsObservable, mediaRepository: room.mediaRepository};
|
||||
}
|
||||
|
||||
async _getMemberDetailsArguments() {
|
||||
const segment = this.navigation.path.get("member");
|
||||
const userId = segment.value;
|
||||
const observableMember = await this._room.observeMember(userId);
|
||||
if (!observableMember) {
|
||||
return false;
|
||||
}
|
||||
const isEncrypted = this._room.isEncrypted;
|
||||
const powerLevelsObservable = await this._room.observePowerLevels();
|
||||
return {observableMember, isEncrypted, powerLevelsObservable, mediaRepository: this._room.mediaRepository};
|
||||
}
|
||||
|
||||
_setupNavigation() {
|
||||
this._hookUpdaterToSegment("details", RoomDetailsViewModel, () => { return {room: this._room}; });
|
||||
this._hookUpdaterToSegment("members", MemberListViewModel, () => this._getMemberArguments());
|
||||
this._hookUpdaterToSegment("members", MemberListViewModel, () => this._getMemberListArguments());
|
||||
this._hookUpdaterToSegment("member", MemberDetailsViewModel, () => this._getMemberDetailsArguments(),
|
||||
() => {
|
||||
// If we fail to create the member details panel, fallback to memberlist
|
||||
const url = `${this.urlCreator.urlUntilSegment("room")}/members`;
|
||||
this.urlCreator.pushUrl(url);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_hookUpdaterToSegment(segment, viewmodel, argCreator) {
|
||||
_hookUpdaterToSegment(segment, viewmodel, argCreator, failCallback) {
|
||||
const observable = this.navigation.observe(segment);
|
||||
const updater = this._setupUpdater(segment, viewmodel, argCreator);
|
||||
this.track(observable.subscribe(() => updater()));
|
||||
const updater = this._setupUpdater(segment, viewmodel, argCreator, failCallback);
|
||||
this.track(observable.subscribe(updater));
|
||||
}
|
||||
|
||||
_setupUpdater(segment, viewmodel, argCreator) {
|
||||
_setupUpdater(segment, viewmodel, argCreator, failCallback) {
|
||||
const updater = async (skipDispose = false) => {
|
||||
if (!skipDispose) {
|
||||
this._activeViewModel = this.disposeTracked(this._activeViewModel);
|
||||
|
@ -53,6 +77,10 @@ export class RightPanelViewModel extends ViewModel {
|
|||
const enable = !!this.navigation.path.get(segment)?.value;
|
||||
if (enable) {
|
||||
const args = await argCreator();
|
||||
if (!args && failCallback) {
|
||||
failCallback();
|
||||
return;
|
||||
}
|
||||
this._activeViewModel = this.track(new viewmodel(this.childOptions(args)));
|
||||
}
|
||||
this.emitChange("activeViewModel");
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.RoomDetailsView {
|
||||
.RoomDetailsView, .MemberDetailsView {
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -15,11 +15,11 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.RoomDetailsView_name h2 {
|
||||
.RoomDetailsView_name h2, .MemberDetailsView_name h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.RoomDetailsView_label, .RoomDetailsView_row, .RoomDetailsView, .EncryptionIconView {
|
||||
.RoomDetailsView_label, .RoomDetailsView_row, .RoomDetailsView, .MemberDetailsView, .EncryptionIconView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.MemberTileView {
|
||||
.MemberTileView a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -807,7 +807,7 @@ button.link {
|
|||
padding-top: 0;
|
||||
}
|
||||
|
||||
.RoomDetailsView_id {
|
||||
.RoomDetailsView_id, .MemberDetailsView_id {
|
||||
color: #737D8C;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
@ -817,7 +817,7 @@ button.link {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.RoomDetailsView_name h2 {
|
||||
.RoomDetailsView_name h2, .MemberDetailsView_name h2 {
|
||||
margin-bottom: 4px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
@ -916,6 +916,11 @@ button.RoomDetailsView_row::after {
|
|||
|
||||
.MemberTileView {
|
||||
margin-bottom: 8px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.MemberTileView a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.MemberTileView .avatar {
|
||||
|
@ -929,6 +934,37 @@ button.RoomDetailsView_row::after {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
/* Member details panel */
|
||||
.MemberDetailsView_section {
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.MemberDetailsView_label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #8d99a5;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.MemberDetailsView_value, .MemberDetailsView_options {
|
||||
margin-left: 8px;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.MemberDetailsView_options {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.MemberDetailsView_options a{
|
||||
color: #0dbd8b;
|
||||
text-decoration: none;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.LazyListParent {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
53
src/platform/web/ui/session/rightpanel/MemberDetailsView.js
Normal file
53
src/platform/web/ui/session/rightpanel/MemberDetailsView.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2021 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.
|
||||
*/
|
||||
|
||||
import {AvatarView} from "../../AvatarView.js";
|
||||
import {TemplateView} from "../../general/TemplateView.js";
|
||||
|
||||
export class MemberDetailsView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "MemberDetailsView"},
|
||||
[ t.view(new AvatarView(vm, 128)),
|
||||
t.div({className: "MemberDetailsView_name"}, t.h2(vm => vm.name)),
|
||||
t.div({className: "MemberDetailsView_id"}, vm.userId),
|
||||
this._createSection(t, vm.i18n`Role`, vm => vm.role),
|
||||
this._createSection(t, vm.i18n`Security`, vm.isEncrypted ?
|
||||
vm.i18n`Messages in this room are end-to-end encrypted.` :
|
||||
vm.i18n`Messages in this room are not end-to-end encrypted.`
|
||||
),
|
||||
this._createOptions(t, vm)
|
||||
]);
|
||||
}
|
||||
|
||||
_createSection(t, label, value) {
|
||||
return t.div({ className: "MemberDetailsView_section" },
|
||||
[
|
||||
t.div({className: "MemberDetailsView_label"}, label),
|
||||
t.div({className: "MemberDetailsView_value"}, value)
|
||||
]);
|
||||
}
|
||||
|
||||
_createOptions(t, vm) {
|
||||
return t.div({ className: "MemberDetailsView_section" },
|
||||
[
|
||||
t.div({className: "MemberDetailsView_label"}, vm.i18n`Options`),
|
||||
t.div({className: "MemberDetailsView_options"},
|
||||
[
|
||||
t.a({href: vm.linkToUser, target: "_blank", rel: "noopener"}, vm.i18n`Open Link to User`)
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -19,9 +19,12 @@ import {AvatarView} from "../../AvatarView.js";
|
|||
|
||||
export class MemberTileView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.li({ className: "MemberTileView" }, [
|
||||
t.view(new AvatarView(vm, 32)),
|
||||
t.div({ className: "MemberTileView_name" }, (vm) => vm.name),
|
||||
]);
|
||||
return t.li({ className: "MemberTileView" },
|
||||
t.a({ href: vm.detailsUrl },
|
||||
[
|
||||
t.view(new AvatarView(vm, 32)),
|
||||
t.div({ className: "MemberTileView_name" }, (vm) => vm.name),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,20 +18,31 @@ import {TemplateView} from "../../general/TemplateView.js";
|
|||
import {RoomDetailsView} from "./RoomDetailsView.js";
|
||||
import {MemberListView} from "./MemberListView.js";
|
||||
import {LoadingView} from "../../general/LoadingView.js";
|
||||
import {MemberDetailsView} from "./MemberDetailsView.js";
|
||||
|
||||
export class RightPanelView extends TemplateView {
|
||||
render(t) {
|
||||
const viewFromType = {
|
||||
"room-details": RoomDetailsView,
|
||||
"member-list": MemberListView
|
||||
};
|
||||
return t.div({ className: "RightPanelView" },
|
||||
[
|
||||
t.ifView(vm => vm.activeViewModel, vm => new ButtonsView(vm)),
|
||||
t.mapView(vm => vm.activeViewModel, vm => vm ? new viewFromType[vm.type](vm) : new LoadingView())
|
||||
t.mapView(vm => vm.activeViewModel, vm => this._viewFromType(vm))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
_viewFromType(vm) {
|
||||
const type = vm?.type;
|
||||
switch (type) {
|
||||
case "room-details":
|
||||
return new RoomDetailsView(vm);
|
||||
case "member-list":
|
||||
return new MemberListView(vm);
|
||||
case "member-details":
|
||||
return new MemberDetailsView(vm);
|
||||
default:
|
||||
return new LoadingView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonsView extends TemplateView {
|
||||
|
|
Loading…
Reference in a new issue