forked from mystiq/hydrogen-web
work on tile view models
This commit is contained in:
parent
d6e357cc22
commit
153d54a285
9 changed files with 153 additions and 15 deletions
|
@ -1,13 +1,42 @@
|
|||
import SimpleTile from "./SimpleTile";
|
||||
|
||||
export default class GapTile extends SimpleTile {
|
||||
constructor(entry, timeline) {
|
||||
super(entry);
|
||||
constructor(options, timeline) {
|
||||
super(options);
|
||||
this._timeline = timeline;
|
||||
this._loading = false;
|
||||
this._error = null;
|
||||
}
|
||||
|
||||
// GapTile specific behaviour
|
||||
fill() {
|
||||
return this._timeline.fillGap(this._entry, 10);
|
||||
async fill() {
|
||||
// prevent doing this twice
|
||||
if (!this._loading) {
|
||||
this._loading = true;
|
||||
this._emitUpdate("isLoading");
|
||||
try {
|
||||
return await this._timeline.fillGap(this._entry, 10);
|
||||
} catch (err) {
|
||||
this._loading = false;
|
||||
this._error = err;
|
||||
this._emitUpdate("isLoading");
|
||||
this._emitUpdate("error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isLoading() {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
get direction() {
|
||||
return this._entry.prev_batch ? "backward" : "forward";
|
||||
}
|
||||
|
||||
get error() {
|
||||
if (this._error) {
|
||||
const dir = this._entry.prev_batch ? "previous" : "next";
|
||||
return `Could not load ${dir} messages: ${this._error.message}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
22
src/domain/session/room/timeline/tiles/ImageTile.js
Normal file
22
src/domain/session/room/timeline/tiles/ImageTile.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import MessageTile from "./MessageTile.js";
|
||||
|
||||
export default class ImageTile extends MessageTile {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
// we start loading the image here,
|
||||
// and call this._emitUpdate once it's loaded?
|
||||
// or maybe we have an becameVisible() callback on tiles where we start loading it?
|
||||
}
|
||||
get src() {
|
||||
return "";
|
||||
}
|
||||
|
||||
get width() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
get height() {
|
||||
return 200;
|
||||
}
|
||||
}
|
20
src/domain/session/room/timeline/tiles/LocationTile.js
Normal file
20
src/domain/session/room/timeline/tiles/LocationTile.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import MessageTile from "./MessageTile.js";
|
||||
|
||||
/*
|
||||
map urls:
|
||||
apple: https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
|
||||
android: https://developers.google.com/maps/documentation/urls/guide
|
||||
wp: maps:49.275267 -122.988617
|
||||
https://www.habaneroconsulting.com/stories/insights/2011/opening-native-map-apps-from-the-mobile-browser
|
||||
*/
|
||||
export default class LocationTile extends MessageTile {
|
||||
get mapsLink() {
|
||||
const geoUri = this._getContent().geo_uri;
|
||||
const [lat, long] = geoUri.split(":")[1].split(",");
|
||||
return `maps:${lat} ${long}`;
|
||||
}
|
||||
|
||||
get label() {
|
||||
return `${this.sender} sent their location, click to see it in maps.`;
|
||||
}
|
||||
}
|
26
src/domain/session/room/timeline/tiles/MessageTile.js
Normal file
26
src/domain/session/room/timeline/tiles/MessageTile.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import SimpleTile from "./SimpleTile.js";
|
||||
|
||||
export default class MessageTile extends SimpleTile {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._date = new Date(this._entry.event.origin_server_ts);
|
||||
}
|
||||
|
||||
get sender() {
|
||||
return this._entry.event.sender;
|
||||
}
|
||||
|
||||
get date() {
|
||||
return this._date.toLocaleDateString();
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this._date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
_getContent() {
|
||||
const event = this._entry.event;
|
||||
return event && event.content;
|
||||
}
|
||||
}
|
9
src/domain/session/room/timeline/tiles/RoomMemberTile.js
Normal file
9
src/domain/session/room/timeline/tiles/RoomMemberTile.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import SimpleTile from "./SimpleTile.js";
|
||||
|
||||
export default class RoomNameTile extends SimpleTile {
|
||||
get label() {
|
||||
const event = this._entry.event;
|
||||
const content = event.content;
|
||||
return `${event.sender} changed membership to ${content.membership}`;
|
||||
}
|
||||
}
|
9
src/domain/session/room/timeline/tiles/RoomNameTile.js
Normal file
9
src/domain/session/room/timeline/tiles/RoomNameTile.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import SimpleTile from "./SimpleTile.js";
|
||||
|
||||
export default class RoomNameTile extends SimpleTile {
|
||||
get label() {
|
||||
const event = this._entry.event;
|
||||
const content = event.content;
|
||||
return `${event.sender} changed the room name to "${content.name}"`
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
export default class SimpleTile {
|
||||
constructor(entry) {
|
||||
constructor({entry, emitUpdate}) {
|
||||
this._entry = entry;
|
||||
this._emitUpdate = emitUpdate;
|
||||
}
|
||||
// view model props for all subclasses
|
||||
// hmmm, could also do instanceof ... ?
|
||||
|
@ -33,10 +34,17 @@ export default class SimpleTile {
|
|||
|
||||
// update received for already included (falls within sort keys) entry
|
||||
updateEntry(entry) {
|
||||
|
||||
// return names of props updated, or true for all, or null for no changes caused
|
||||
return true;
|
||||
}
|
||||
|
||||
// simple entry can only contain 1 entry
|
||||
// return whether the tile should be removed
|
||||
// as SimpleTile only has one entry, the tile should be removed
|
||||
removeEntry(entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// SimpleTile can only contain 1 entry
|
||||
tryIncludeEntry() {
|
||||
return false;
|
||||
}
|
||||
|
|
8
src/domain/session/room/timeline/tiles/TextTile.js
Normal file
8
src/domain/session/room/timeline/tiles/TextTile.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import MessageTile from "./MessageTile.js";
|
||||
|
||||
export default class TextTile extends MessageTile {
|
||||
get text() {
|
||||
const content = this._getContent();
|
||||
return content && content.body;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
import GapTile from "./tiles/GapTile.js";
|
||||
import TextTile from "./tiles/TextTile.js";
|
||||
import ImageTile from "./tiles/ImageTile.js";
|
||||
import LocationTile from "./tiles/LocationTile.js";
|
||||
import RoomNameTile from "./tiles/RoomNameTile.js";
|
||||
import RoomMemberTile from "./tiles/RoomMemberTile.js";
|
||||
|
||||
export default function ({timeline}) {
|
||||
export default function ({timeline, emitUpdate}) {
|
||||
return function tilesCreator(entry) {
|
||||
const options = {entry, emitUpdate};
|
||||
if (entry.gap) {
|
||||
return new GapTile(entry, timeline);
|
||||
return new GapTile(options, timeline);
|
||||
} else if (entry.event) {
|
||||
const event = entry.event;
|
||||
switch (event.type) {
|
||||
|
@ -16,18 +18,23 @@ export default function ({timeline}) {
|
|||
const msgtype = content && content.msgtype;
|
||||
switch (msgtype) {
|
||||
case "m.text":
|
||||
return new TextTile(entry);
|
||||
case "m.notice":
|
||||
return new TextTile(options);
|
||||
case "m.image":
|
||||
return new ImageTile(entry);
|
||||
return new ImageTile(options);
|
||||
case "m.location":
|
||||
return new LocationTile(options);
|
||||
default:
|
||||
return null; // unknown tile types are not rendered?
|
||||
// unknown msgtype not rendered
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case "m.room.name":
|
||||
return new RoomNameTile(entry);
|
||||
return new RoomNameTile(options);
|
||||
case "m.room.member":
|
||||
return new RoomMemberTile(entry);
|
||||
return new RoomMemberTile(options);
|
||||
default:
|
||||
// unknown type not rendered
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue