implement first draft of image lightbox

This commit is contained in:
Bruno Windels 2020-10-30 15:20:11 +01:00
parent 137264edcb
commit c9147e6b9a
6 changed files with 161 additions and 6 deletions

View file

@ -207,7 +207,9 @@ export class SessionViewModel extends ViewModel {
this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel);
}
if (eventId) {
this._lightboxViewModel = this.track(new LightboxViewModel(this.childOptions({eventId})));
const roomId = this.navigation.path.get("room").value;
const room = this._sessionContainer.session.rooms.get(roomId);
this._lightboxViewModel = this.track(new LightboxViewModel(this.childOptions({eventId, room})));
}
this.emitChange("lightboxViewModel");
}

View file

@ -20,14 +20,78 @@ export class LightboxViewModel extends ViewModel {
constructor(options) {
super(options);
this._eventId = options.eventId;
this._unencryptedImageUrl = null;
this._decryptedImage = null;
this._closeUrl = this.urlCreator.urlUntilSegment("room");
this._eventEntry = null;
this._date = null;
this._subscribeToEvent(options.room, options.eventId);
}
get eventId() {
return this._eventId;
_subscribeToEvent(room, eventId) {
const eventObservable = room.observeEvent(eventId);
this.track(eventObservable.subscribe(eventEntry => {
this._loadEvent(room, eventEntry);
}));
this._loadEvent(room, eventObservable.get());
}
async _loadEvent(room, eventEntry) {
if (!eventEntry) {
return;
}
const {mediaRepository} = room;
this._eventEntry = eventEntry;
const {content} = this._eventEntry;
this._date = this._eventEntry.timestamp ? new Date(this._eventEntry.timestamp) : null;
if (content.url) {
this._unencryptedImageUrl = mediaRepository.mxcUrl(content.url);
this.emitChange("imageUrl");
} else if (content.file) {
this._decryptedImage = this.track(await mediaRepository.downloadEncryptedFile(content.file));
this.emitChange("imageUrl");
}
}
get imageWidth() {
return this._eventEntry?.content?.info?.w;
}
get imageHeight() {
return this._eventEntry?.content?.info?.h;
}
get name() {
return this._eventEntry?.content?.body;
}
get sender() {
return this._eventEntry?.displayName;
}
get imageUrl() {
if (this._decryptedImage) {
return this._decryptedImage.url;
} else if (this._unencryptedImageUrl) {
return this._unencryptedImageUrl;
} else {
return "";
}
}
get date() {
return this._date && this._date.toLocaleDateString({}, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
}
get time() {
return this._date && this._date.toLocaleTimeString({}, {hour: "numeric", minute: "2-digit"});
}
get closeUrl() {
return this._closeUrl;
}
close() {
this.platform.history.pushUrl(this.closeUrl);
}
}

View file

@ -109,7 +109,6 @@ main {
use numeric positions because named grid areas
are not present in mobile layout */
grid-area: 2 / 1 / 3 / 3;
background-color: rgba(0,0,0,0.5);
/* this should not be necessary, but chrome seems to have a bug when there are scrollbars in other grid items,
it seems to put the scroll areas on top of the other grid items unless they have a z-index */
z-index: 1;

View file

@ -632,3 +632,63 @@ button.link {
color: #03B381;
font-weight: 600;
}
.lightbox {
background-color: rgba(0,0,0,0.75);
display: grid;
grid-template:
"content close" auto
"content details" 1fr /
1fr auto;
color: white;
}
@media (max-aspect-ratio: 1/1) {
.lightbox {
grid-template:
"close" auto
"content" 1fr
"details" auto /
1fr;
}
.lightbox .details {
width: 100% !important;
}
}
.lightbox .picture {
grid-area: content;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
width: 100%;
height: 100%;
margin: auto;
}
.lightbox .loading {
grid-area: content;
margin: auto;
}
.lightbox .close {
grid-area: close;
margin-left: auto;
background-image: url('icons/dismiss.svg');
background-position: center;
background-size: 16px;
background-repeat: no-repeat;
width: 16px;
height: 16px;
padding: 12px;
}
.lightbox .details {
grid-area: details;
padding: 12px;
font-size: 1.5rem;
width: 200px;
}

View file

@ -35,7 +35,7 @@ export class LoginView extends TemplateView {
});
const homeserver = t.input({
id: "homeserver",
type: "text",
type: "url",
placeholder: vm.i18n`Your matrix homeserver`,
value: vm.defaultHomeServer,
disabled

View file

@ -15,9 +15,39 @@ limitations under the License.
*/
import {TemplateView} from "../../general/TemplateView.js";
import {spinner} from "../../common.js";
export class LightboxView extends TemplateView {
render(t, vm) {
return t.div({className: "lightbox"}, [vm.eventId, t.br(), t.a({href: vm.closeUrl}, "close")]);
const close = t.a({href: vm.closeUrl, title: vm.i18n`Close`, className: "close"});
const image = t.div({
role: "img",
"aria-label": vm => vm.name,
title: vm => vm.name,
className: {
picture: true,
hidden: vm => !vm.imageUrl,
},
style: vm => `background-image: url('${vm.imageUrl}'); max-width: ${vm.imageWidth}px; max-height: ${vm.imageHeight}px;`
});
const loading = t.div({
className: {
loading: true,
hidden: vm => !!vm.imageUrl
}
}, [
spinner(t),
t.div(vm.i18n`Loading image…`)
]);
const details = t.div({
className: "details"
}, [t.strong(vm => vm.name), t.br(), "uploaded by ", t.strong(vm => vm.sender), vm => ` at ${vm.time} on ${vm.date}.`]);
return t.div({className: "lightbox", onClick: evt => this.close(evt)}, [image, loading, details, close]);
}
close(evt) {
if (evt.target === this.root()) {
this.value.close();
}
}
}