forked from mystiq/hydrogen-web
create room view and view model
This commit is contained in:
parent
a1e14c4eec
commit
5c085efc10
8 changed files with 294 additions and 12 deletions
126
src/domain/session/CreateRoomViewModel.js
Normal file
126
src/domain/session/CreateRoomViewModel.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 {imageToInfo} from "./common.js";
|
||||||
|
import {RoomType} from "../../matrix/room/create";
|
||||||
|
|
||||||
|
export class CreateRoomViewModel extends ViewModel {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
const {session} = options;
|
||||||
|
this._session = session;
|
||||||
|
this._name = "";
|
||||||
|
this._topic = "";
|
||||||
|
this._isPublic = false;
|
||||||
|
this._isEncrypted = true;
|
||||||
|
this._avatarScaledBlob = undefined;
|
||||||
|
this._avatarFileName = undefined;
|
||||||
|
this._avatarInfo = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setName(name) {
|
||||||
|
this._name = name;
|
||||||
|
this.emitChange("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() { return this._name; }
|
||||||
|
|
||||||
|
setTopic(topic) {
|
||||||
|
this._topic = topic;
|
||||||
|
this.emitChange("topic");
|
||||||
|
}
|
||||||
|
|
||||||
|
get topic() { return this._topic; }
|
||||||
|
|
||||||
|
setPublic(isPublic) {
|
||||||
|
this._isPublic = isPublic;
|
||||||
|
this.emitChange("isPublic");
|
||||||
|
}
|
||||||
|
|
||||||
|
get isPublic() { return this._isPublic; }
|
||||||
|
|
||||||
|
setEncrypted(isEncrypted) {
|
||||||
|
this._isEncrypted = isEncrypted;
|
||||||
|
this.emitChange("isEncrypted");
|
||||||
|
}
|
||||||
|
|
||||||
|
get isEncrypted() { return this._isEncrypted; }
|
||||||
|
|
||||||
|
get canCreate() {
|
||||||
|
return !!this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
let avatar;
|
||||||
|
if (this._avatarScaledBlob) {
|
||||||
|
avatar = {
|
||||||
|
info: this._avatarInfo,
|
||||||
|
name: this._avatarFileName,
|
||||||
|
blob: this._avatarScaledBlob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const roomBeingCreated = this._session.createRoom({
|
||||||
|
type: this.isPublic ? RoomType.Public : RoomType.Private,
|
||||||
|
name: this.name ?? undefined,
|
||||||
|
topic: this.topic ?? undefined,
|
||||||
|
isEncrypted: !this.isPublic && this._isEncrypted,
|
||||||
|
alias: this.isPublic ? this.roomAlias : undefined,
|
||||||
|
avatar,
|
||||||
|
invites: ["@bwindels:matrix.org"]
|
||||||
|
});
|
||||||
|
this.navigation.push("room", roomBeingCreated.localId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
avatarUrl() { return this._avatarScaledBlob.url; }
|
||||||
|
get avatarTitle() { return this.name; }
|
||||||
|
get avatarLetter() { return ""; }
|
||||||
|
get avatarColorNumber() { return 0; }
|
||||||
|
get hasAvatar() { return !!this._avatarScaledBlob; }
|
||||||
|
get error() { return ""; }
|
||||||
|
|
||||||
|
async selectAvatar() {
|
||||||
|
if (!this.platform.hasReadPixelPermission()) {
|
||||||
|
alert("Please allow canvas image data access, so we can scale your images down.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._avatarScaledBlob) {
|
||||||
|
this._avatarScaledBlob.dispose();
|
||||||
|
}
|
||||||
|
this._avatarScaledBlob = undefined;
|
||||||
|
this._avatarFileName = undefined;
|
||||||
|
this._avatarInfo = undefined;
|
||||||
|
|
||||||
|
const file = await this.platform.openFile("image/*");
|
||||||
|
if (!file || !file.blob.mimeType.startsWith("image/")) {
|
||||||
|
// allow to clear the avatar by not selecting an image
|
||||||
|
this.emitChange("hasAvatar");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let image = await this.platform.loadImage(file.blob);
|
||||||
|
const limit = 800;
|
||||||
|
if (image.maxDimension > limit) {
|
||||||
|
const scaledImage = await image.scale(limit);
|
||||||
|
image.dispose();
|
||||||
|
image = scaledImage;
|
||||||
|
}
|
||||||
|
this._avatarScaledBlob = image.blob;
|
||||||
|
this._avatarInfo = imageToInfo(image);
|
||||||
|
this._avatarFileName = file.name;
|
||||||
|
this.emitChange("hasAvatar");
|
||||||
|
}
|
||||||
|
}
|
24
src/domain/session/common.js
Normal file
24
src/domain/session/common.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function imageToInfo(image) {
|
||||||
|
return {
|
||||||
|
w: image.width,
|
||||||
|
h: image.height,
|
||||||
|
mimetype: image.blob.mimeType,
|
||||||
|
size: image.blob.size
|
||||||
|
};
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import {ComposerViewModel} from "./ComposerViewModel.js"
|
||||||
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
|
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
|
||||||
import {tilesCreator} from "./timeline/tilesCreator.js";
|
import {tilesCreator} from "./timeline/tilesCreator.js";
|
||||||
import {ViewModel} from "../../ViewModel.js";
|
import {ViewModel} from "../../ViewModel.js";
|
||||||
|
import {imageToInfo} from "../common.js";
|
||||||
|
|
||||||
export class RoomViewModel extends ViewModel {
|
export class RoomViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
@ -273,7 +274,9 @@ export class RoomViewModel extends ViewModel {
|
||||||
let image = await this.platform.loadImage(file.blob);
|
let image = await this.platform.loadImage(file.blob);
|
||||||
const limit = await this.platform.settingsStorage.getInt("sentImageSizeLimit");
|
const limit = await this.platform.settingsStorage.getInt("sentImageSizeLimit");
|
||||||
if (limit && image.maxDimension > limit) {
|
if (limit && image.maxDimension > limit) {
|
||||||
image = await image.scale(limit);
|
const scaledImage = await image.scale(limit);
|
||||||
|
image.dispose();
|
||||||
|
image = scaledImage;
|
||||||
}
|
}
|
||||||
const content = {
|
const content = {
|
||||||
body: file.name,
|
body: file.name,
|
||||||
|
@ -319,15 +322,6 @@ export class RoomViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function imageToInfo(image) {
|
|
||||||
return {
|
|
||||||
w: image.width,
|
|
||||||
h: image.height,
|
|
||||||
mimetype: image.blob.mimeType,
|
|
||||||
size: image.blob.size
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function videoToInfo(video) {
|
function videoToInfo(video) {
|
||||||
const info = imageToInfo(video);
|
const info = imageToInfo(video);
|
||||||
info.duration = video.duration;
|
info.duration = video.duration;
|
||||||
|
|
|
@ -34,6 +34,7 @@ limitations under the License.
|
||||||
.hydrogen .avatar img {
|
.hydrogen .avatar img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* work around postcss-css-variables limitations and repeat variable usage */
|
/* work around postcss-css-variables limitations and repeat variable usage */
|
||||||
|
|
|
@ -49,7 +49,7 @@ main {
|
||||||
grid-template:
|
grid-template:
|
||||||
"status status" auto
|
"status status" auto
|
||||||
"left middle" 1fr /
|
"left middle" 1fr /
|
||||||
300px 1fr;
|
320px 1fr;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ main {
|
||||||
grid-template:
|
grid-template:
|
||||||
"status status status" auto
|
"status status status" auto
|
||||||
"left middle right" 1fr /
|
"left middle right" 1fr /
|
||||||
300px 1fr 300px;
|
320px 1fr 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* resize and reposition session view to account for mobile Safari which shifts
|
/* resize and reposition session view to account for mobile Safari which shifts
|
||||||
|
|
3
src/platform/web/ui/css/themes/element/icons/plus.svg
Normal file
3
src/platform/web/ui/css/themes/element/icons/plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.74986 3.55554C8.74986 3.14133 8.41408 2.80554 7.99986 2.80554C7.58565 2.80554 7.24986 3.14133 7.24986 3.55554V7.24999L3.55542 7.24999C3.14121 7.24999 2.80542 7.58577 2.80542 7.99999C2.80542 8.4142 3.14121 8.74999 3.55542 8.74999L7.24987 8.74999V12.4444C7.24987 12.8586 7.58565 13.1944 7.99987 13.1944C8.41408 13.1944 8.74987 12.8586 8.74987 12.4444V8.74999L12.4443 8.74999C12.8585 8.74999 13.1943 8.4142 13.1943 7.99999C13.1943 7.58577 12.8585 7.24999 12.4443 7.24999L8.74986 7.24999V3.55554Z" fill="#8E99A4"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 670 B |
|
@ -171,6 +171,10 @@ a.button-action {
|
||||||
background-image: url('icons/settings.svg');
|
background-image: url('icons/settings.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-utility.create {
|
||||||
|
background-image: url('icons/plus.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.button-utility.grid.on {
|
.button-utility.grid.on {
|
||||||
background-image: url('icons/disable-grid.svg');
|
background-image: url('icons/disable-grid.svg');
|
||||||
}
|
}
|
||||||
|
@ -1089,3 +1093,28 @@ button.RoomDetailsView_row::after {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CreateRoomView {
|
||||||
|
padding: 0 12px;
|
||||||
|
justify-self: center;
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CreateRoomView_selectAvatar {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CreateRoomView_selectAvatarPlaceholder {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: #e1e3e6;
|
||||||
|
background-image: url('icons/plus.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 36px;
|
||||||
|
}
|
||||||
|
|
105
src/platform/web/ui/session/CreateRoomView.js
Normal file
105
src/platform/web/ui/session/CreateRoomView.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
Copyright 2020 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 {TemplateView} from "../general/TemplateView";
|
||||||
|
import {AvatarView} from "../AvatarView";
|
||||||
|
import {StaticView} from "../general/StaticView";
|
||||||
|
|
||||||
|
export class CreateRoomView extends TemplateView {
|
||||||
|
render(t, vm) {
|
||||||
|
return t.main({className: "CreateRoomView middle"}, [
|
||||||
|
t.h2("Create room"),
|
||||||
|
//t.div({className: "RoomView_error"}, vm => vm.error),
|
||||||
|
t.form({className: "CreateRoomView_detailsForm form", onChange: evt => this.onFormChange(evt), onSubmit: evt => this.onSubmit(evt)}, [
|
||||||
|
t.div({className: "vertical-layout"}, [
|
||||||
|
t.button({type: "button", className: "CreateRoomView_selectAvatar", onClick: () => vm.selectAvatar()},
|
||||||
|
t.mapView(vm => vm.hasAvatar, hasAvatar => {
|
||||||
|
if (hasAvatar) {
|
||||||
|
return new AvatarView(vm, 64);
|
||||||
|
} else {
|
||||||
|
return new StaticView(undefined, t => {
|
||||||
|
return t.div({className: "CreateRoomView_selectAvatarPlaceholder"})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
t.div({className: "stretch form-row text"}, [
|
||||||
|
t.label({for: "name"}, vm.i18n`Room name`),
|
||||||
|
t.input({
|
||||||
|
onInput: evt => vm.setName(evt.target.value),
|
||||||
|
type: "text", name: "name", id: "name",
|
||||||
|
placeholder: vm.i18n`Enter a room name`
|
||||||
|
}, vm => vm.name),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
t.div({className: "form-row text"}, [
|
||||||
|
t.label({for: "topic"}, vm.i18n`Topic (optional)`),
|
||||||
|
t.textarea({
|
||||||
|
onInput: evt => vm.setTopic(evt.target.value),
|
||||||
|
name: "topic", id: "topic",
|
||||||
|
placeholder: vm.i18n`Topic`
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
t.div({className: "form-group"}, [
|
||||||
|
t.div({className: "form-row check"}, [
|
||||||
|
t.input({type: "radio", name: "isPublic", id: "isPrivate", value: "false", checked: !vm.isPublic}),
|
||||||
|
t.label({for: "isPrivate"}, vm.i18n`Private room, only upon invitation.`)
|
||||||
|
]),
|
||||||
|
t.div({className: "form-row check"}, [
|
||||||
|
t.input({type: "radio", name: "isPublic", id: "isPublic", value: "true", checked: vm.isPublic}),
|
||||||
|
t.label({for: "isPublic"}, vm.i18n`Public room, anyone can join`)
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
t.div({className: {"form-row check": true, hidden: vm => vm.isPublic}}, [
|
||||||
|
t.input({type: "checkbox", name: "isEncrypted", id: "isEncrypted", checked: vm.isEncrypted}),
|
||||||
|
t.label({for: "isEncrypted"}, vm.i18n`Enable end-to-end encryption`)
|
||||||
|
]),
|
||||||
|
t.div({className: {"form-row text": true, hidden: vm => !vm.isPublic}}, [
|
||||||
|
t.label({for: "roomAlias"}, vm.i18n`Room alias`),
|
||||||
|
t.input({
|
||||||
|
onInput: evt => vm.setRoomAlias(evt.target.value),
|
||||||
|
type: "text", name: "roomAlias", id: "roomAlias",
|
||||||
|
placeholder: vm.i18n`Room alias
|
||||||
|
`}),
|
||||||
|
]),
|
||||||
|
t.div({className: "button-row"}, [
|
||||||
|
t.button({
|
||||||
|
className: "button-action primary",
|
||||||
|
type: "submit",
|
||||||
|
disabled: vm => !vm.canCreate
|
||||||
|
}, vm.i18n`Create room`),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormChange(evt) {
|
||||||
|
switch (evt.target.name) {
|
||||||
|
case "isEncrypted":
|
||||||
|
this.value.setEncrypted(evt.target.checked);
|
||||||
|
break;
|
||||||
|
case "isPublic":
|
||||||
|
this.value.setPublic(evt.currentTarget.isPublic.value === "true");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.value.create();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue