Merge pull request #162 from vector-im/bwindels/settings-screen

Basic setting screen
This commit is contained in:
Bruno Windels 2020-10-19 13:20:36 +00:00 committed by GitHub
commit 8f89c7b363
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 256 additions and 35 deletions

View File

@ -125,7 +125,10 @@ export class RootViewModel extends ViewModel {
_showSession(sessionContainer) { _showSession(sessionContainer) {
this._setSection(() => { this._setSection(() => {
this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer})); this._sessionViewModel = new SessionViewModel(this.childOptions({
sessionContainer,
updateService: this.getOption("updateService")
}));
this._sessionViewModel.start(); this._sessionViewModel.start();
}); });
} }

View File

@ -34,6 +34,11 @@ export class ViewModel extends EventEmitter {
return Object.assign({navigation, urlCreator, clock}, explicitOptions); return Object.assign({navigation, urlCreator, clock}, explicitOptions);
} }
// makes it easier to pass through dependencies of a sub-view model
getOption(name) {
return this._options[name];
}
track(disposable) { track(disposable) {
if (!this.disposables) { if (!this.disposables) {
this.disposables = new Disposables(); this.disposables = new Disposables();

View File

@ -19,6 +19,7 @@ import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
import {RoomViewModel} from "./room/RoomViewModel.js"; import {RoomViewModel} from "./room/RoomViewModel.js";
import {SessionStatusViewModel} from "./SessionStatusViewModel.js"; import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
import {RoomGridViewModel} from "./RoomGridViewModel.js"; import {RoomGridViewModel} from "./RoomGridViewModel.js";
import {SettingsViewModel} from "./SettingsViewModel.js";
import {ViewModel} from "../ViewModel.js"; import {ViewModel} from "../ViewModel.js";
export class SessionViewModel extends ViewModel { export class SessionViewModel extends ViewModel {
@ -34,6 +35,7 @@ export class SessionViewModel extends ViewModel {
this._leftPanelViewModel = this.track(new LeftPanelViewModel(this.childOptions({ this._leftPanelViewModel = this.track(new LeftPanelViewModel(this.childOptions({
rooms: this._sessionContainer.session.rooms rooms: this._sessionContainer.session.rooms
}))); })));
this._settingsViewModel = null;
this._currentRoomViewModel = null; this._currentRoomViewModel = null;
this._gridViewModel = null; this._gridViewModel = null;
this._setupNavigation(); this._setupNavigation();
@ -53,12 +55,18 @@ export class SessionViewModel extends ViewModel {
// this gives us the active room // this gives us the active room
this.track(currentRoomId.subscribe(roomId => { this.track(currentRoomId.subscribe(roomId => {
if (!this._gridViewModel) { if (!this._gridViewModel) {
this._openRoom(roomId); this._updateRoom(roomId);
} }
})); }));
if (currentRoomId.get() && !this._gridViewModel) { if (!this._gridViewModel) {
this._openRoom(currentRoomId.get()); this._updateRoom(currentRoomId.get());
} }
const settings = this.navigation.observe("settings");
this.track(settings.subscribe(settingsOpen => {
this._updateSettings(settingsOpen);
}));
this._updateSettings(settings.get());
} }
get id() { get id() {
@ -74,6 +82,8 @@ export class SessionViewModel extends ViewModel {
return this._currentRoomViewModel.id; return this._currentRoomViewModel.id;
} else if (this._gridViewModel) { } else if (this._gridViewModel) {
return "roomgrid"; return "roomgrid";
} else if (this._settingsViewModel) {
return "settings";
} }
return "placeholder"; return "placeholder";
} }
@ -90,6 +100,10 @@ export class SessionViewModel extends ViewModel {
return this._sessionStatusViewModel; return this._sessionStatusViewModel;
} }
get settingsViewModel() {
return this._settingsViewModel;
}
get roomList() { get roomList() {
return this._roomList; return this._roomList;
} }
@ -148,7 +162,7 @@ export class SessionViewModel extends ViewModel {
return roomVM; return roomVM;
} }
_openRoom(roomId) { _updateRoom(roomId) {
if (!roomId) { if (!roomId) {
if (this._currentRoomViewModel) { if (this._currentRoomViewModel) {
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel); this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
@ -167,4 +181,17 @@ export class SessionViewModel extends ViewModel {
} }
this.emitChange("currentRoom"); this.emitChange("currentRoom");
} }
_updateSettings(settingsOpen) {
if (this._settingsViewModel) {
this._settingsViewModel = this.disposeTracked(this._settingsViewModel);
}
if (settingsOpen) {
this._settingsViewModel = this.track(new SettingsViewModel(this.childOptions({
updateService: this.getOption("updateService"),
session: this._sessionContainer.session
})));
}
this.emitChange("activeSection");
}
} }

View File

@ -0,0 +1,65 @@
/*
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 {ViewModel} from "../ViewModel.js";
export class SettingsViewModel extends ViewModel {
constructor(options) {
super(options);
this._updateService = options.updateService;
this._session = options.session;
this._closeUrl = this.urlCreator.urlUntilSegment("session");
}
get closeUrl() {
return this._closeUrl;
}
get fingerprintKey() {
const key = this._session.fingerprintKey;
const partLength = 4;
const partCount = Math.ceil(key.length / partLength);
let formattedKey = "";
for (let i = 0; i < partCount; i += 1) {
formattedKey += (formattedKey.length ? " " : "") + key.slice(i * partLength, (i + 1) * partLength);
}
return formattedKey;
}
get deviceId() {
return this._session.deviceId;
}
get userId() {
return this._session.userId;
}
get version() {
if (this._updateService) {
return `${this._updateService.version} (${this._updateService.buildHash})`;
}
return "development version";
}
checkForUpdate() {
this._updateService?.checkForUpdate();
}
get showUpdateButton() {
return !!this._updateService;
}
}

View File

@ -45,12 +45,17 @@ export class LeftPanelViewModel extends ViewModel {
this._currentTileVM = null; this._currentTileVM = null;
this._setupNavigation(); this._setupNavigation();
this._closeUrl = this.urlCreator.urlForSegment("session"); this._closeUrl = this.urlCreator.urlForSegment("session");
this._settingsUrl = this.urlCreator.urlForSegment("settings");
} }
get closeUrl() { get closeUrl() {
return this._closeUrl; return this._closeUrl;
} }
get settingsUrl() {
return this._settingsUrl;
}
_setupNavigation() { _setupNavigation() {
const roomObservable = this.navigation.observe("room"); const roomObservable = this.navigation.observe("room");
this.track(roomObservable.subscribe(roomId => this._open(roomId))); this.track(roomObservable.subscribe(roomId => this._open(roomId)));

View File

@ -77,6 +77,18 @@ export class Session {
this.needsSessionBackup = new ObservableValue(false); this.needsSessionBackup = new ObservableValue(false);
} }
get fingerprintKey() {
return this._e2eeAccount?.identityKeys.ed25519;
}
get deviceId() {
return this._sessionInfo.deviceId;
}
get userId() {
return this._sessionInfo.userId;
}
// called once this._e2eeAccount is assigned // called once this._e2eeAccount is assigned
_setupEncryption() { _setupEncryption() {
console.log("loaded e2ee account with keys", this._e2eeAccount.identityKeys); console.log("loaded e2ee account with keys", this._e2eeAccount.identityKeys);

View File

@ -51,17 +51,18 @@ html {
width: 100vw; width: 100vw;
} }
/* hide back button in middle section by default */
.middle .close-middle { display: none; }
/* mobile layout */ /* mobile layout */
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
/* show back button */ /* show back button */
.RoomHeader .close-room { display: block !important; } .middle .close-middle { display: block !important; }
/* hide grid button */ /* hide grid button */
.LeftPanel .grid { display: none !important; } .LeftPanel .grid { display: none !important; }
div.RoomView, div.room-placeholder, div.RoomGridView { display: none; } div.middle, div.room-placeholder { display: none; }
div.LeftPanel {flex-grow: 1;} div.LeftPanel {flex-grow: 1;}
div.room-shown div.RoomGridView { display: flex; } div.middle-shown div.middle { display: flex; }
div.room-shown div.RoomView { display: flex; } div.middle-shown div.LeftPanel { display: none; }
div.room-shown div.LeftPanel { display: none; }
div.right-shown div.TimelinePanel { display: none; } div.right-shown div.TimelinePanel { display: none; }
} }
@ -70,7 +71,7 @@ html {
min-width: 0; min-width: 0;
} }
.room-placeholder, .RoomView, .RoomGridView { .room-placeholder, .middle {
flex: 1 0 0; flex: 1 0 0;
min-width: 0; min-width: 0;
} }
@ -94,7 +95,7 @@ html {
flex: 1 0 0; flex: 1 0 0;
} }
.RoomHeader { .middle-header {
display: flex; display: flex;
} }

View File

@ -15,23 +15,19 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.RoomHeader { .middle-header {
align-items: center; align-items: center;
} }
.RoomHeader h2 { .middle-header h2 {
flex: 1; flex: 1;
} }
.RoomHeader button { .middle-header button {
display: block; display: block;
} }
.RoomHeader .close-room { .middle-header .room-description {
display: none;
}
.RoomHeader .room-description {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
@ -47,7 +43,7 @@ limitations under the License.
min-width: 0; min-width: 0;
} }
.RoomHeader h2 { .middle-header h2 {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3625 9.2875C19.5625 9.8125 20.075 10.1625 20.6375 10.1625C21.3875 10.1625 22 10.775 22 11.525V12.475C22 13.225 21.3875 13.8375 20.6375 13.8375C20.075 13.8375 19.5625 14.1875 19.3625 14.7125C19.346 14.7538 19.3294 14.7958 19.3128 14.838C19.2538 14.9876 19.1932 15.1413 19.125 15.2875C18.8875 15.8 19 16.4 19.4 16.8C19.9375 17.325 19.9375 18.1875 19.4 18.725L18.725 19.4C18.2 19.9375 17.3375 19.9375 16.8 19.4C16.4125 19 15.8 18.8875 15.2875 19.125C15.1 19.2125 14.9125 19.2875 14.7125 19.3625C14.1875 19.5625 13.8375 20.075 13.8375 20.6375C13.8375 21.3875 13.225 22 12.475 22H11.525C10.775 22 10.1625 21.3875 10.1625 20.6375C10.1625 20.075 9.8125 19.5625 9.2875 19.3625C9.24617 19.346 9.20423 19.3294 9.16195 19.3128C9.01243 19.2538 8.85867 19.1932 8.7125 19.125C8.2 18.8875 7.6 19 7.2 19.4C6.675 19.9375 5.8125 19.9375 5.275 19.4L4.6 18.725C4.0625 18.2 4.0625 17.3375 4.6 16.8C5 16.4125 5.1125 15.8 4.875 15.2875C4.7875 15.1 4.7125 14.9125 4.6375 14.7125C4.4375 14.1875 3.925 13.8375 3.3625 13.8375C2.6125 13.8375 2 13.225 2 12.475V11.525C2 10.775 2.6125 10.1625 3.3625 10.1625C3.925 10.1625 4.4375 9.8125 4.6375 9.2875C4.67694 9.16129 4.72634 9.04005 4.77627 8.91751C4.80546 8.84587 4.83483 8.77379 4.8625 8.7C5.1 8.1875 4.9875 7.5875 4.5875 7.1875C4.05 6.6625 4.05 5.8 4.5875 5.2625L5.275 4.6C5.8 4.0625 6.6625 4.0625 7.2 4.6C7.5875 5 8.2 5.1125 8.7125 4.875C8.9 4.7875 9.0875 4.7 9.2875 4.6375C9.8125 4.4375 10.1625 3.925 10.1625 3.3625C10.1625 2.6125 10.775 2 11.525 2H12.475C13.225 2 13.8375 2.6125 13.8375 3.3625C13.8375 3.9375 14.1875 4.4375 14.7125 4.6375C14.7538 4.65403 14.7958 4.67056 14.838 4.68723C14.9876 4.74617 15.1413 4.80679 15.2875 4.875C15.8 5.1125 16.4 5 16.8 4.6C17.325 4.0625 18.1875 4.0625 18.725 4.6L19.4 5.275C19.9375 5.8 19.9375 6.6625 19.4 7.2C19 7.5875 18.8875 8.2 19.125 8.7125C19.2125 8.9 19.2875 9.0875 19.3625 9.2875ZM12 17C9.2375 17 7 14.7625 7 12C7 9.2375 9.2375 7 12 7C14.7625 7 17 9.2375 17 12C17 14.7625 14.7625 17 12 17Z" fill="#8D99A5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -132,12 +132,17 @@ a.button-action {
background-repeat: no-repeat; background-repeat: no-repeat;
border: none; border: none;
border-radius: 100%; border-radius: 100%;
display: block;
} }
.button-utility.grid { .button-utility.grid {
background-image: url('icons/enable-grid.svg'); background-image: url('icons/enable-grid.svg');
} }
.button-utility.settings {
background-image: url('icons/settings.svg');
}
.button-utility.grid.on { .button-utility.grid.on {
background-image: url('icons/disable-grid.svg'); background-image: url('icons/disable-grid.svg');
} }
@ -311,7 +316,7 @@ a {
z-index: 2; z-index: 2;
} }
.room-shown .SessionStatusView { .middle-shown .SessionStatusView {
top: 72px; top: 72px;
} }
@ -382,7 +387,7 @@ a {
border-radius: 12px; border-radius: 12px;
} }
.RoomHeader { .middle-header {
box-sizing: border-box; box-sizing: border-box;
height: 58px; /* 12 + 36 + 12 to align with filter field + margin */ height: 58px; /* 12 + 36 + 12 to align with filter field + margin */
background: white; background: white;
@ -390,19 +395,19 @@ a {
border-bottom: 1px solid rgba(245, 245, 245, 0.90); border-bottom: 1px solid rgba(245, 245, 245, 0.90);
} }
.RoomHeader h2 { .middle-header h2 {
font-size: 1.8rem; font-size: 1.8rem;
font-weight: 600; font-weight: 600;
} }
.RoomHeader > :not(:last-child) { .middle-header > :not(:last-child) {
/* use margin-right because the first item, /* use margin-right because the first item,
.close-room might be hidden and then we don't .close-middle might be hidden and then we don't
want a margin-left on the second item*/ want a margin-left on the second item*/
margin-right: 8px; margin-right: 8px;
} }
.close-room, .close-session { .close-middle, .close-session {
background-image: url('icons/chevron-left.svg'); background-image: url('icons/chevron-left.svg');
background-position-x: 10px; background-position-x: 10px;
} }
@ -541,3 +546,40 @@ ul.Timeline > li.messageStatus .message-container > p {
.GapView > :not(:first-child) { .GapView > :not(:first-child) {
margin-left: 12px; margin-left: 12px;
} }
.SettingsBody {
padding: 12px 16px;
}
.Settings .row .label {
font-weight: 600;
}
.Settings .row .label, .Settings .row .content {
margin-top: 4px;
margin-bottom: 4px;
}
.Settings .row .content {
margin-left: 4px;
}
.Settings .row.code .content {
font-family: monospace;
}
.Settings .row .content button {
display: inline-block;
margin: 0 8px;
}
.Settings .row {
margin: 4px 0px;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.Settings .row .label {
flex: 0 0 200px;
}

View File

@ -152,6 +152,14 @@ export class ServiceWorkerHandler {
this._registration.update(); this._registration.update();
} }
get version() {
return window.HYDROGEN_VERSION;
}
get buildHash() {
return window.HYDROGEN_GLOBAL_HASH;
}
async preventConcurrentSessionAccess(sessionId) { async preventConcurrentSessionAccess(sessionId) {
return this._sendAndWaitForReply("closeSession", {sessionId}); return this._sendAndWaitForReply("closeSession", {sessionId});
} }

View File

@ -1,5 +1,5 @@
/* /*
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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -42,6 +42,6 @@ export class RoomGridView extends TemplateView {
}))); })));
} }
children.push(t.div({className: vm => `focus-ring tile${vm.focusIndex}`})); children.push(t.div({className: vm => `focus-ring tile${vm.focusIndex}`}));
return t.div({className: "RoomGridView layout3x2"}, children); return t.div({className: "RoomGridView middle layout3x2"}, children);
} }
} }

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2020 Bruno Windels <bruno@windels.cloud> 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -20,13 +21,14 @@ import {TemplateView} from "../general/TemplateView.js";
import {StaticView} from "../general/StaticView.js"; import {StaticView} from "../general/StaticView.js";
import {SessionStatusView} from "./SessionStatusView.js"; import {SessionStatusView} from "./SessionStatusView.js";
import {RoomGridView} from "./RoomGridView.js"; import {RoomGridView} from "./RoomGridView.js";
import {SettingsView} from "./SettingsView.js";
export class SessionView extends TemplateView { export class SessionView extends TemplateView {
render(t, vm) { render(t, vm) {
return t.div({ return t.div({
className: { className: {
"SessionView": true, "SessionView": true,
"room-shown": vm => vm.activeSection !== "placeholder" "middle-shown": vm => vm.activeSection !== "placeholder"
}, },
}, [ }, [
t.view(new SessionStatusView(vm.sessionStatusViewModel)), t.view(new SessionStatusView(vm.sessionStatusViewModel)),
@ -38,6 +40,8 @@ export class SessionView extends TemplateView {
return new RoomGridView(vm.roomGridViewModel); return new RoomGridView(vm.roomGridViewModel);
case "placeholder": case "placeholder":
return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`))); return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`)));
case "settings":
return new SettingsView(vm.settingsViewModel);
default: //room id default: //room id
return new RoomView(vm.currentRoomViewModel); return new RoomView(vm.currentRoomViewModel);
} }

View File

@ -0,0 +1,49 @@
/*
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.js";
export class SettingsView extends TemplateView {
render(t, vm) {
let version = vm.version;
if (vm.showUpdateButton) {
version = t.span([
vm.version,
t.button({onClick: () => vm.checkForUpdate()}, vm.i18n`Check for updates`)
]);
}
const row = (label, content, extraClass = "") => {
return t.div({className: `row ${extraClass}`}, [
t.div({className: "label"}, label),
t.div({className: "content"}, content),
]);
};
return t.main({className: "Settings middle"}, [
t.div({className: "middle-header"}, [
t.a({className: "button-utility close-middle", href: vm.closeUrl, title: vm.i18n`Close settings`}),
t.h2("Settings")
]),
t.div({className: "SettingsBody"}, [
row(vm.i18n`User ID`, vm.userId),
row(vm.i18n`Session ID`, vm.deviceId, "code"),
row(vm.i18n`Session key`, vm.fingerprintKey, "code"),
row(vm.i18n`Version`, version),
])
]);
}
}

View File

@ -57,7 +57,7 @@ export class LeftPanelView extends TemplateView {
vm.i18n`Enable grid layout`; vm.i18n`Enable grid layout`;
}; };
const utilitiesRow = t.div({className: "utilities"}, [ const utilitiesRow = t.div({className: "utilities"}, [
t.a({className: "button-utility close-session", href: vm.closeUrl}), t.a({className: "button-utility close-session", href: vm.closeUrl, "aria-label": vm.i18n`Back to account list`, title: vm.i18n`Back to account list`}),
t.view(new FilterField({ t.view(new FilterField({
i18n: vm.i18n, i18n: vm.i18n,
label: vm.i18n`Filter rooms…`, label: vm.i18n`Filter rooms…`,
@ -75,7 +75,8 @@ export class LeftPanelView extends TemplateView {
}, },
title: gridButtonLabel, title: gridButtonLabel,
"aria-label": gridButtonLabel "aria-label": gridButtonLabel
}) }),
t.a({className: "button-utility settings", href: vm.settingsUrl, "aria-label": vm.i18n`Settings`, title: vm.i18n`Settings`}),
]); ]);
return t.div({className: "LeftPanel"}, [ return t.div({className: "LeftPanel"}, [

View File

@ -23,10 +23,10 @@ import {renderAvatar} from "../../common.js";
export class RoomView extends TemplateView { export class RoomView extends TemplateView {
render(t, vm) { render(t, vm) {
return t.div({className: "RoomView"}, [ return t.main({className: "RoomView middle"}, [
t.div({className: "TimelinePanel"}, [ t.div({className: "TimelinePanel"}, [
t.div({className: "RoomHeader"}, [ t.div({className: "RoomHeader middle-header"}, [
t.a({className: "button-utility close-room", href: vm.closeUrl, title: vm.i18n`Close room`}), t.a({className: "button-utility close-middle", href: vm.closeUrl, title: vm.i18n`Close room`}),
renderAvatar(t, vm, 32), renderAvatar(t, vm, 32),
t.div({className: "room-description"}, [ t.div({className: "room-description"}, [
t.h2(vm => vm.name), t.h2(vm => vm.name),