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

Peeking functionality #981

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
35dd8f1
define PeekableRoomView and make UnknownRoomViewModel handle it
ashfame Jan 9, 2023
6332ff7
define PeekableRoom class and peek via unknownRoomViewModel, setting …
ashfame Jan 11, 2023
39863b6
cleanup
ashfame Jan 13, 2023
98f3add
have peeking functionality show up messages in the room atleast
ashfame Jan 31, 2023
6c9a5ee
add composer bar at the bottom
ashfame Jan 31, 2023
bebd9a8
insert all messages correctly with correct order
ashfame Feb 1, 2023
4765486
better default
ashfame Feb 1, 2023
ae6d606
remove peekable store created since we are not storing peekable room …
ashfame Feb 1, 2023
692afed
show room name and move description to view itself
ashfame Feb 2, 2023
76abe49
remove unused property
ashfame Feb 2, 2023
ae28157
hide context menu button on each message
ashfame Feb 2, 2023
deb65e3
revert formatting changes to file & such
ashfame Feb 2, 2023
b88601d
remove unused imports
ashfame Feb 2, 2023
2272885
load messages in the opposite order - recent to specified bunch in th…
ashfame Feb 2, 2023
f1a8db4
refactor fetching events to a separate function so that it can be inv…
ashfame Feb 2, 2023
aaf86cc
simplify preparing options for api call
ashfame Feb 3, 2023
74fe88c
prepare room summary dynamically
ashfame Feb 3, 2023
4fe2947
call peek() outside of constructor, do it when the object is created
ashfame Feb 3, 2023
d84d09f
style bottom bar
ashfame Feb 3, 2023
eeb42f6
show room's picture in header
ashfame Feb 3, 2023
732a95f
load 100 messages in the first go
ashfame Feb 3, 2023
0930107
quick & dirty way to disable link on avatars that trigger right panel…
ashfame Feb 6, 2023
6a45b2c
purge old records before peeking on a fresh load
ashfame Feb 7, 2023
b420712
remove console log calls
ashfame Feb 7, 2023
71f2800
fix args
ashfame Feb 8, 2023
c306b49
use getter
ashfame Feb 8, 2023
7755c75
remove PeekableRoom since we didn't end up needing it
ashfame Feb 8, 2023
b7ae858
remove room summary code since that's not required for peeking
ashfame Feb 8, 2023
810b523
Revert "remove room summary code since that's not required for peeking"
ashfame Feb 8, 2023
921cd0a
remove summary properties that are not used in peeking
ashfame Feb 8, 2023
eb66ecc
remove getter
ashfame Feb 8, 2023
3e2f3ab
remove close button that's not even shown
ashfame Feb 8, 2023
3e48ab0
remove redundant css rule
ashfame Feb 8, 2023
e200710
ensure peek() method is not invokable on un-peekable room
ashfame Feb 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/domain/session/SessionViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,26 @@ export class SessionViewModel extends ViewModel {
return null;
}

_createUnknownRoomViewModel(roomIdOrAlias) {
return new UnknownRoomViewModel(this.childOptions({
async _createUnknownRoomViewModel(roomIdOrAlias) {
let roomId;
if ( roomIdOrAlias[0] !== "!" ) {
let response = await this._client._requestScheduler.hsApi.resolveRoomAlias(roomIdOrAlias).response();
roomId = response.room_id;
} else {
roomId = roomIdOrAlias;
}
const peekable = await this._client.session.canPeekInRoom(roomId);

let vm = new UnknownRoomViewModel(this.childOptions({
roomIdOrAlias,
session: this._client.session,
peekable: peekable
}));
if (peekable) {
await vm.peek();
}

return vm;
}

async _createArchivedRoomViewModel(roomId) {
Expand Down
48 changes: 46 additions & 2 deletions src/domain/session/room/UnknownRoomViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,33 @@ limitations under the License.
*/

import {ViewModel} from "../../ViewModel";
import {TimelineViewModel} from "./timeline/TimelineViewModel";
import {tileClassForEntry as defaultTileClassForEntry} from "./timeline/tiles/index";
import {getAvatarHttpUrl} from "../../avatar";

export class UnknownRoomViewModel extends ViewModel {
constructor(options) {
super(options);
const {roomIdOrAlias, session} = options;
const {roomIdOrAlias, session, peekable} = options;
this._session = session;
this.roomIdOrAlias = roomIdOrAlias;
this._peekable = peekable;
this._error = null;
this._busy = false;
}

get peekable() {
return this._peekable;
}

get error() {
return this._error?.message;
}

get room() {
return this._room;
}

async join() {
this._busy = true;
this.emitChange("busy");
Expand All @@ -53,6 +65,38 @@ export class UnknownRoomViewModel extends ViewModel {
}

get kind() {
return "unknown";
return this._peekable ? "peekable" : "unknown";
}

get timelineViewModel() {
return this._timelineVM;
}

avatarUrl(size) {
return getAvatarHttpUrl(this._room.avatarUrl, size, this.platform, this._room.mediaRepository);
}

async peek() {
if ( !this._peekable ) {
return;
}
try {
this._room = await this._session.loadPeekableRoom(this.roomIdOrAlias);
const timeline = await this._room.openTimeline();
this._tileOptions = this.childOptions({
roomVM: this,
timeline,
tileClassForEntry: defaultTileClassForEntry,
});
this._timelineVM = this.track(new TimelineViewModel(this.childOptions({
tileOptions: this._tileOptions,
timeline,
})));
this.emitChange("timelineViewModel");
} catch (err) {
console.error(`room.openTimeline(): ${err.message}:\n${err.stack}`);
this._timelineError = err;
this.emitChange("error");
}
}
}
117 changes: 117 additions & 0 deletions src/matrix/Session.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import {
} from "./ssss/index";
import {SecretStorage} from "./ssss/SecretStorage";
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue";
import {EventKey} from "./room/timeline/EventKey";
import {createEventEntry} from "./room/timeline/persistence/common";

const PICKLE_KEY = "DEFAULT_KEY";
const PUSHER_KEY = "pusher";
Expand Down Expand Up @@ -585,6 +587,22 @@ export class Session {
return room;
}

/** @internal */
_createPeekableRoom(roomId) {
return new Room({
roomId,
getSyncToken: this._getSyncToken,
storage: this._storage,
emitCollectionChange: this._roomUpdateCallback,
hsApi: this._hsApi,
mediaRepository: this._mediaRepository,
pendingEvents: [],
user: this._user,
createRoomEncryption: this._createRoomEncryption,
platform: this._platform
});
}

get invites() {
return this._invites;
}
Expand Down Expand Up @@ -942,12 +960,111 @@ export class Session {
});
}

loadPeekableRoom(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "loadPeekableRoom", async log => {
log.set("id", roomId);

const room = this._createPeekableRoom(roomId);
let response = await this._loadEventsPeekableRoom(roomId, 100, 'b', null, log);
// Note: response.end to be used in the next call for sync functionality

let summary = await this._preparePeekableRoomSummary(roomId, log);
const txn = await this._storage.readTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
this._storage.storeNames.roomMembers,
]);
await room.load(summary, txn, log);

return room;
});
}

async _preparePeekableRoomSummary(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "preparePeekableRoomSummary", async log => {
log.set("id", roomId);

let summary = {};
const resp = await this._hsApi.currentState(roomId).response();
for ( let i=0; i<resp.length; i++ ) {
if ( resp[i].type === 'm.room.name') {
summary["name"] = resp[i].content.name;
} else if ( resp[i].type === 'm.room.canonical_alias' ) {
summary["canonicalAlias"] = resp[i].content.alias;
} else if ( resp[i].type === 'm.room.avatar' ) {
summary["avatarUrl"] = resp[i].content.url;
}
}

return summary;
});
}

async _loadEventsPeekableRoom(roomId, limit = 30, dir = 'b', end = null, log = null) {
return this._platform.logger.wrapOrRun(log, "loadEventsPeekableRoom", async log => {
log.set("id", roomId);
let options = {
limit: limit,
dir: 'b',
filter: {
lazy_load_members: true,
include_redundant_members: true,
}
}
if (end !== null) {
options['from'] = end;
}

const response = await this._hsApi.messages(roomId, options, {log}).response();
log.set("/messages endpoint response", response);

const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
]);

// clear old records for this room
txn.timelineFragments.removeAllForRoom(roomId);
txn.timelineEvents.removeAllForRoom(roomId);

// insert fragment and event records for this room
const fragment = {
roomId: roomId,
id: 0,
previousId: null,
nextId: null,
previousToken: response.start,
nextToken: null,
};
txn.timelineFragments.add(fragment);

let eventKey = EventKey.defaultLiveKey;
for (let i = 0; i < response.chunk.length; i++) {
if (i) {
eventKey = eventKey.previousKey();
}
let txn = await this._storage.readWriteTxn([this._storage.storeNames.timelineEvents]);
let eventEntry = createEventEntry(eventKey, roomId, response.chunk[i]);
await txn.timelineEvents.tryInsert(eventEntry, log);
}

return response;
});
}

joinRoom(roomIdOrAlias, log = null) {
return this._platform.logger.wrapOrRun(log, "joinRoom", async log => {
const body = await this._hsApi.joinIdOrAlias(roomIdOrAlias, {log}).response();
return body.room_id;
});
}

canPeekInRoom(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "canPeekInRoom", async log => {
const body = await this._hsApi.state(roomId, 'm.room.history_visibility', '', {log}).response();
return body.history_visibility === 'world_readable';
});
}
}

export function tests() {
Expand Down
8 changes: 8 additions & 0 deletions src/matrix/net/HomeServerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export class HomeServerApi {
return this._get("/sync", {since, timeout, filter}, undefined, options);
}

resolveRoomAlias(roomAlias: string): IHomeServerRequest {
return this._unauthedRequest( "GET", this._url( `/directory/room/${encodeURIComponent(roomAlias)}`, CS_V3_PREFIX ) );
}

context(roomId: string, eventId: string, limit: number, filter: string): IHomeServerRequest {
return this._get(`/rooms/${encodeURIComponent(roomId)}/context/${encodeURIComponent(eventId)}`, {filter, limit});
}
Expand Down Expand Up @@ -160,6 +164,10 @@ export class HomeServerApi {
return this._get(`/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(eventType)}/${encodeURIComponent(stateKey)}`, {}, undefined, options);
}

currentState(roomId: string): IHomeServerRequest {
return this._get(`/rooms/${encodeURIComponent(roomId)}/state`, {}, undefined);
}

getLoginFlows(): IHomeServerRequest {
return this._unauthedRequest("GET", this._url("/login"));
}
Expand Down
13 changes: 12 additions & 1 deletion src/platform/web/ui/css/themes/element/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ button.link {
width: 100%;
}

.DisabledComposerView {
.DisabledComposerView, .PeekableRoomComposerView {
padding: 12px;
background-color: var(--background-color-secondary);
}
Expand All @@ -977,6 +977,17 @@ button.link {
width: 100%;
}

.PeekableRoomView .Timeline_message:hover > .Timeline_messageOptions{
display: none;
}
.PeekableRoomComposerView h3 {
display: inline-block;
margin: 0;
}
.PeekableRoomComposerView .joinRoomButton {
float: right;
}

.LoadingView {
height: 100%;
width: 100%;
Expand Down
3 changes: 3 additions & 0 deletions src/platform/web/ui/session/SessionView.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {CreateRoomView} from "./CreateRoomView.js";
import {RightPanelView} from "./rightpanel/RightPanelView.js";
import {viewClassForTile} from "./room/common";
import {JoinRoomView} from "./JoinRoomView";
import {PeekableRoomView} from "./room/PeekableRoomView";

export class SessionView extends TemplateView {
render(t, vm) {
Expand Down Expand Up @@ -58,6 +59,8 @@ export class SessionView extends TemplateView {
return new RoomView(vm.currentRoomViewModel, viewClassForTile);
} else if (vm.currentRoomViewModel.kind === "roomBeingCreated") {
return new RoomBeingCreatedView(vm.currentRoomViewModel);
} else if (vm.currentRoomViewModel.kind === "peekable") {
return new PeekableRoomView(vm.currentRoomViewModel, viewClassForTile);
} else {
return new UnknownRoomView(vm.currentRoomViewModel);
}
Expand Down
41 changes: 41 additions & 0 deletions src/platform/web/ui/session/room/PeekableRoomView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {TemplateView} from "../../general/TemplateView";
import {TimelineView} from "./TimelineView";
import {TimelineLoadingView} from "./TimelineLoadingView";
import {AvatarView} from "../../AvatarView";

export class PeekableRoomView extends TemplateView {

constructor(vm, viewClassForTile) {
super(vm);
this._viewClassForTile = viewClassForTile;
}

render(t, vm) {
return t.main({className: "RoomView PeekableRoomView middle"}, [
t.div({className: "RoomHeader middle-header"}, [
t.view(new AvatarView(vm, 32)),
t.div({className: "room-description"}, [
t.h2(vm => vm.room.name),
]),
]),
t.div({className: "RoomView_body"}, [
t.div({className: "RoomView_error"}, [
t.if(vm => vm.error, t => t.div(
[
t.p({}, vm => vm.error),
t.button({ className: "RoomView_error_closerButton", onClick: evt => vm.dismissError(evt) })
])
)]),
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
return timelineViewModel ?
new TimelineView(timelineViewModel, this._viewClassForTile) :
new TimelineLoadingView(vm); // vm is just needed for i18n
}),
t.div({className: "PeekableRoomComposerView"}, [
t.h3(vm => vm.i18n`Join the room to participate`),
t.button({className: "joinRoomButton", onClick: () => vm.join()}, vm.i18n`Join Room`)
])
])
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class BaseMessageView extends TemplateView {
li.removeChild(li.querySelector(".Timeline_messageAvatar"));
li.removeChild(li.querySelector(".Timeline_messageSender"));
} else if (!isContinuation && !this._isReplyPreview) {
const avatar = tag.a({href: vm.memberPanelLink, className: "Timeline_messageAvatar"}, [renderStaticAvatar(vm, 30)]);
const avatar = vm.options.peekable ? tag.div({className: "Timeline_messageAvatar"}, [renderStaticAvatar(vm, 30)]) : tag.a({href: vm.memberPanelLink, className: "Timeline_messageAvatar"}, [renderStaticAvatar(vm, 30)]);
const sender = tag.div({className: `Timeline_messageSender usercolor${vm.avatarColorNumber}`}, vm.displayName);
li.insertBefore(avatar, li.firstChild);
li.insertBefore(sender, li.firstChild);
Expand Down