forked from mystiq/hydrogen-web
show images intimeline
This commit is contained in:
parent
fba760dfc1
commit
7f221cda65
6 changed files with 98 additions and 18 deletions
|
@ -1,26 +1,48 @@
|
||||||
import {MessageTile} from "./MessageTile.js";
|
import {MessageTile} from "./MessageTile.js";
|
||||||
|
|
||||||
|
const MAX_HEIGHT = 300;
|
||||||
|
const MAX_WIDTH = 400;
|
||||||
|
|
||||||
export class ImageTile extends MessageTile {
|
export class ImageTile extends MessageTile {
|
||||||
constructor(options) {
|
constructor(options, room) {
|
||||||
super(options);
|
super(options);
|
||||||
|
this._room = room;
|
||||||
// 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() {
|
get thumbnailUrl() {
|
||||||
return 200;
|
const mxcUrl = this._getContent().url;
|
||||||
|
return this._room.mxcUrlThumbnail(mxcUrl, this.thumbnailWidth, this.thumbnailHeigth, "scale");
|
||||||
}
|
}
|
||||||
|
|
||||||
get height() {
|
get url() {
|
||||||
return 200;
|
const mxcUrl = this._getContent().url;
|
||||||
|
return this._room.mxcUrl(mxcUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
_scaleFactor() {
|
||||||
|
const {info} = this._getContent();
|
||||||
|
const scaleHeightFactor = MAX_HEIGHT / info.h;
|
||||||
|
const scaleWidthFactor = MAX_WIDTH / info.w;
|
||||||
|
// take the smallest scale factor, to respect all constraints
|
||||||
|
// we should not upscale images, so limit scale factor to 1 upwards
|
||||||
|
return Math.min(scaleWidthFactor, scaleHeightFactor, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get thumbnailWidth() {
|
||||||
|
const {info} = this._getContent();
|
||||||
|
return Math.round(info.w * this._scaleFactor());
|
||||||
|
}
|
||||||
|
|
||||||
|
get thumbnailHeigth() {
|
||||||
|
const {info} = this._getContent();
|
||||||
|
return Math.round(info.h * this._scaleFactor());
|
||||||
}
|
}
|
||||||
|
|
||||||
get label() {
|
get label() {
|
||||||
return "this is an image";
|
return this._getContent().body;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shape() {
|
||||||
|
return "image";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {GapTile} from "./tiles/GapTile.js";
|
import {GapTile} from "./tiles/GapTile.js";
|
||||||
import {TextTile} from "./tiles/TextTile.js";
|
import {TextTile} from "./tiles/TextTile.js";
|
||||||
|
import {ImageTile} from "./tiles/ImageTile.js";
|
||||||
import {LocationTile} from "./tiles/LocationTile.js";
|
import {LocationTile} from "./tiles/LocationTile.js";
|
||||||
import {RoomNameTile} from "./tiles/RoomNameTile.js";
|
import {RoomNameTile} from "./tiles/RoomNameTile.js";
|
||||||
import {RoomMemberTile} from "./tiles/RoomMemberTile.js";
|
import {RoomMemberTile} from "./tiles/RoomMemberTile.js";
|
||||||
|
@ -20,8 +21,7 @@ export function tilesCreator({room, ownUserId}) {
|
||||||
case "m.emote":
|
case "m.emote":
|
||||||
return new TextTile(options);
|
return new TextTile(options);
|
||||||
case "m.image":
|
case "m.image":
|
||||||
return null; // not supported yet
|
return new ImageTile(options, room);
|
||||||
// return new ImageTile(options);
|
|
||||||
case "m.location":
|
case "m.location":
|
||||||
return new LocationTile(options);
|
return new LocationTile(options);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -73,8 +73,8 @@ export class HomeServerApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_request(method, url, queryParams, body, options) {
|
_encodeQueryParams(queryParams) {
|
||||||
const queryString = Object.entries(queryParams || {})
|
return Object.entries(queryParams || {})
|
||||||
.filter(([, value]) => value !== undefined)
|
.filter(([, value]) => value !== undefined)
|
||||||
.map(([name, value]) => {
|
.map(([name, value]) => {
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
|
@ -83,6 +83,10 @@ export class HomeServerApi {
|
||||||
return `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
return `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
||||||
})
|
})
|
||||||
.join("&");
|
.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
_request(method, url, queryParams, body, options) {
|
||||||
|
const queryString = this._encodeQueryParams(queryParams);
|
||||||
url = `${url}?${queryString}`;
|
url = `${url}?${queryString}`;
|
||||||
let bodyString;
|
let bodyString;
|
||||||
const headers = new Map();
|
const headers = new Map();
|
||||||
|
@ -166,6 +170,35 @@ export class HomeServerApi {
|
||||||
versions(options = null) {
|
versions(options = null) {
|
||||||
return this._request("GET", `${this._homeserver}/_matrix/client/versions`, null, null, options);
|
return this._request("GET", `${this._homeserver}/_matrix/client/versions`, null, null, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_parseMxcUrl(url) {
|
||||||
|
const prefix = "mxc://";
|
||||||
|
if (url.startsWith(prefix)) {
|
||||||
|
return url.substr(prefix.length).split("/", 2);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mxcUrlThumbnail(url, width, height, method) {
|
||||||
|
const parts = this._parseMxcUrl(url);
|
||||||
|
if (parts) {
|
||||||
|
const [serverName, mediaId] = parts;
|
||||||
|
const httpUrl = `${this._homeserver}/_matrix/media/r0/thumbnail/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
||||||
|
return httpUrl + "?" + this._encodeQueryParams({width, height, method});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mxcUrl(url) {
|
||||||
|
const parts = this._parseMxcUrl(url);
|
||||||
|
if (parts) {
|
||||||
|
const [serverName, mediaId] = parts;
|
||||||
|
return `${this._homeserver}/_matrix/media/r0/download/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tests() {
|
export function tests() {
|
||||||
|
|
|
@ -130,5 +130,13 @@ export class Room extends EventEmitter {
|
||||||
await this._timeline.load();
|
await this._timeline.load();
|
||||||
return this._timeline;
|
return this._timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mxcUrlThumbnail(url, width, height, method) {
|
||||||
|
return this._hsApi.mxcUrlThumbnail(url, width, height, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
mxcUrl(url) {
|
||||||
|
return this._hsApi.mxcUrl(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {ListView} from "../../general/ListView.js";
|
import {ListView} from "../../general/ListView.js";
|
||||||
import {GapView} from "./timeline/GapView.js";
|
import {GapView} from "./timeline/GapView.js";
|
||||||
import {TextMessageView} from "./timeline/TextMessageView.js";
|
import {TextMessageView} from "./timeline/TextMessageView.js";
|
||||||
|
import {ImageView} from "./timeline/ImageView.js";
|
||||||
import {AnnouncementView} from "./timeline/AnnouncementView.js";
|
import {AnnouncementView} from "./timeline/AnnouncementView.js";
|
||||||
|
|
||||||
export class TimelineList extends ListView {
|
export class TimelineList extends ListView {
|
||||||
|
@ -10,7 +11,8 @@ export class TimelineList extends ListView {
|
||||||
switch (entry.shape) {
|
switch (entry.shape) {
|
||||||
case "gap": return new GapView(entry);
|
case "gap": return new GapView(entry);
|
||||||
case "announcement": return new AnnouncementView(entry);
|
case "announcement": return new AnnouncementView(entry);
|
||||||
case "message":return new TextMessageView(entry);
|
case "message": return new TextMessageView(entry);
|
||||||
|
case "image": return new ImageView(entry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._atBottom = false;
|
this._atBottom = false;
|
||||||
|
|
15
src/ui/web/session/room/timeline/ImageView.js
Normal file
15
src/ui/web/session/room/timeline/ImageView.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {TemplateView} from "../../../general/TemplateView.js";
|
||||||
|
|
||||||
|
export class ImageView extends TemplateView {
|
||||||
|
render(t, vm) {
|
||||||
|
return t.li(
|
||||||
|
{className: {"TextMessageView": true, own: vm.isOwn, pending: vm.isPending}},
|
||||||
|
t.div({className: "message-container"}, [
|
||||||
|
t.div({className: "sender"}, vm => vm.isContinuation ? "" : vm.sender),
|
||||||
|
t.div(t.a({href: vm.url, target: "_blank"},
|
||||||
|
t.img({src: vm.thumbnailUrl, width: vm.thumbnailWidth, heigth: vm.thumbnailHeigth, loading: "lazy", alt: vm.label}))),
|
||||||
|
t.p(t.time(vm.date + " " + vm.time)),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue