diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index 4f3b3cbd..42be2ae1 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -32,39 +32,51 @@ export class LoginViewModel extends ViewModel { this._passwordLoginViewModel = null; this._startSSOLoginViewModel = null; this._completeSSOLoginViewModel = null; - this._start(); + this._homeserver = null; + this._errorMessage = ""; + this._start(this._defaultHomeServer); } get passwordLoginViewModel() { return this._passwordLoginViewModel; } get startSSOLoginViewModel() { return this._startSSOLoginViewModel; } get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; } + get defaultHomeServer() { return this._defaultHomeServer; } + get errorMessage() { return this._errorMessage; } - async _start() { + async _start(homeserver) { if (this._loginToken) { this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(this.childOptions({loginToken: this._loginToken}))); this.emitChange("completeSSOLoginViewModel"); } else { - await this.queryLogin(this._defaultHomeServer); - this._showPasswordLogin(); - this._showSSOLogin(this._defaultHomeServer); + this._errorMessage = ""; + await this.queryLogin(homeserver); + if (this._loginOptions) { + if (this._loginOptions.sso) { this._showSSOLogin(); } + if (this._loginOptions.password) { this._showPasswordLogin(); } + if (!this._loginOptions.sso && !this._loginOptions.password) { + this._showError("This homeserver neither supports SSO nor Password based login flows"); + } + } + else { + this._showError("Could not query login methods supported by the homeserver"); + } } } _showPasswordLogin() { - this._passwordLoginViewModel = new PasswordLoginViewModel(this.childOptions({defaultHomeServer: this._defaultHomeServer})); - const observable = this._passwordLoginViewModel.homeserverObservable; - this.track(observable.subscribe(newHomeServer => this._onHomeServerChange(newHomeServer))); + this._passwordLoginViewModel = this.track(new PasswordLoginViewModel(this.childOptions())); this.emitChange("passwordLoginViewModel"); } - _showSSOLogin(homeserver) { - this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); + _showSSOLogin() { + this._startSSOLoginViewModel = this.track(new StartSSOLoginViewModel(this.childOptions())); this.emitChange("startSSOLoginViewModel"); - if (this._loginOptions?.sso && !this._loginToken) { - this._startSSOLoginViewModel = this.track(new StartSSOLoginViewModel(this.childOptions({homeserver}))); - this.emitChange("startSSOLoginViewModel"); - } + } + + _showError(message) { + this._errorMessage = message; + this.emitChange("errorMessage"); } async queryLogin(homeserver) { @@ -73,16 +85,22 @@ export class LoginViewModel extends ViewModel { } catch (e) { this._loginOptions = null; - console.error("Could not query login methods supported by the homeserver"); } } - async _onHomeServerChange(homeserver) { - await this.queryLogin(homeserver); - this._showSSOLogin(homeserver); + _disposeViewModels() { + this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel); + this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel); + this.emitChange("disposeViewModels"); } - childOptions(options) { + updateHomeServer(newHomeserver) { + this._homeserver = newHomeserver; + this._disposeViewModels(); + this._start(newHomeserver); + } + + childOptions(options = {}) { return { ...super.childOptions(options), ready: sessionContainer => { @@ -91,7 +109,8 @@ export class LoginViewModel extends ViewModel { this._ready(sessionContainer); }, sessionContainer: this._sessionContainer, - loginOptions: this._loginOptions + loginOptions: this._loginOptions, + homeserver: this._homeserver ?? this._defaultHomeServer } } diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 5feee9b5..32aef2dd 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -16,30 +16,22 @@ limitations under the License. import {ViewModel} from "../ViewModel.js"; import {SessionLoadViewModel} from "../SessionLoadViewModel.js"; -import {ObservableValue} from "../../observable/ObservableValue.js"; export class PasswordLoginViewModel extends ViewModel { constructor(options) { super(options); - const {ready, defaultHomeServer, loginOptions, sessionContainer} = options; + const {ready, loginOptions, sessionContainer, homeserver} = options; this._ready = ready; - this._defaultHomeServer = defaultHomeServer; this._sessionContainer = sessionContainer; this._loadViewModel = null; this._loadViewModelSubscription = null; this._loginOptions = loginOptions; - this._homeserverObservable = new ObservableValue(this._defaultHomeServer); + this._homeserver = homeserver; } - get defaultHomeServer() { return this._defaultHomeServer; } get loadViewModel() {return this._loadViewModel; } - get homeserverObservable() { return this._homeserverObservable; } get cancelUrl() { return this.urlCreator.urlForSegment("session"); } - updateHomeServer(homeserver) { - this._homeserverObservable.set(homeserver); - } - get isBusy() { if (!this._loadViewModel) { return false; @@ -48,7 +40,8 @@ export class PasswordLoginViewModel extends ViewModel { } } - async login(username, password, homeserver) { + async login(username, password) { + const homeserver = this._homeserver; if (!this._loginOptions.password) { const path = this.navigation.pathFrom([this.navigation.segment("session")]); this.navigation.applyPath(path); diff --git a/src/platform/web/ui/css/login.css b/src/platform/web/ui/css/login.css index b6f474dd..2b65f82c 100644 --- a/src/platform/web/ui/css/login.css +++ b/src/platform/web/ui/css/login.css @@ -50,8 +50,8 @@ limitations under the License. margin: 0 20px; } -.LoginView { - padding: 0.4em; +.PasswordLoginView { + padding: 0 0.4em 0.4em; } .SessionLoadStatusView { @@ -81,7 +81,7 @@ limitations under the License. margin-top: 10px; } -.StartSSOLoginView_separator { +.LoginView_separator { justify-content: center; display: flex; margin: 8px; @@ -91,3 +91,11 @@ limitations under the License. display: flex; justify-content: center; } + +.LoginView_sso { + padding: 0.4em 0.4em 0; +} + +.LoginView_error { + padding: 0.4em +} diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 74a1f5f8..facbefc8 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -228,7 +228,7 @@ a.button-action { border-radius: 8px; } -.StartSSOLoginView_separator { +.LoginView_separator { font-weight: 500; font-size: 1.5rem; } diff --git a/src/platform/web/ui/login/LoginView.js b/src/platform/web/ui/login/LoginView.js index 45c773bd..f260314d 100644 --- a/src/platform/web/ui/login/LoginView.js +++ b/src/platform/web/ui/login/LoginView.js @@ -20,12 +20,25 @@ import {PasswordLoginView} from "./PasswordLoginView.js"; import {CompleteSSOView} from "./CompleteSSOView.js"; export class LoginView extends TemplateView { - render(t) { - return t.div({ className: "PreSessionScreen" }, [ - t.div({ className: "logo" }), - t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm? new CompleteSSOView(vm): null), - t.mapView(vm => vm.passwordLoginViewModel, vm => vm? new PasswordLoginView(vm): null), - t.mapView(vm => vm.startSSOLoginViewModel, vm => vm? new StartSSOLoginView(vm): null), + render(t, vm) { + const homeserver = t.input({ + id: "homeserver", + type: "text", + placeholder: vm.i18n`Your matrix homeserver`, + value: vm.defaultHomeServer, + onChange: () => vm.updateHomeServer(homeserver.value), + }); + + return t.div({className: "PreSessionScreen"}, [ + t.div({className: "logo"}), + t.h1([vm.i18n`Sign In`]), + t.mapView(vm => vm.completeSSOLoginViewModel, vm => vm ? new CompleteSSOView(vm) : null), + t.if(vm => !vm.completeSSOLoginViewModel, + (t, vm) => t.div({ className: "LoginView_sso form form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver])), + t.mapView(vm => vm.passwordLoginViewModel, vm => vm ? new PasswordLoginView(vm): null), + t.if(vm => vm.passwordLoginViewModel && vm.startSSOLoginViewModel, t => t.p({className: "LoginView_separator"}, vm.i18n`or`)), + t.mapView(vm => vm.startSSOLoginViewModel, vm => vm ? new StartSSOLoginView(vm) : null), + t.if(vm => vm.errorMessage, (t, vm) => t.h5({className: "LoginView_error"}, vm.i18n(vm.errorMessage))), // use t.mapView rather than t.if to create a new view when the view model changes too t.p(hydrogenGithubLink(t)) ]); @@ -35,10 +48,11 @@ export class LoginView extends TemplateView { class StartSSOLoginView extends TemplateView { render(t, vm) { return t.div({ className: "StartSSOLoginView" }, - [ - t.p({ className: "StartSSOLoginView_separator" }, "or"), - t.button({ className: "StartSSOLoginView_button button-action secondary", type: "button", onClick: () => vm.startSSOLogin() }, "Log in with SSO") - ] + t.button({ + className: "StartSSOLoginView_button button-action secondary", + type: "button", + onClick: () => vm.startSSOLogin() + }, vm.i18n`Log in with SSO`) ); } } diff --git a/src/platform/web/ui/login/PasswordLoginView.js b/src/platform/web/ui/login/PasswordLoginView.js index 7a41f2b5..b19daa2a 100644 --- a/src/platform/web/ui/login/PasswordLoginView.js +++ b/src/platform/web/ui/login/PasswordLoginView.js @@ -32,27 +32,17 @@ export class PasswordLoginView extends TemplateView { placeholder: vm.i18n`Password`, disabled }); - const homeserver = t.input({ - id: "homeserver", - type: "text", - placeholder: vm.i18n`Your matrix homeserver`, - value: vm.defaultHomeServer, - onChange: () => vm.updateHomeServer(homeserver.value), - disabled - }); - return t.div({className: "LoginView form"}, [ - t.h1([vm.i18n`Sign In`]), + return t.div({className: "PasswordLoginView form"}, [ t.if(vm => vm.error, t => t.div({ className: "error" }, vm => vm.error)), t.form({ onSubmit: evnt => { evnt.preventDefault(); - vm.login(username.value, password.value, homeserver.value); + vm.login(username.value, password.value); } }, [ t.div({ className: "form-row" }, [t.label({ for: "username" }, vm.i18n`Username`), username]), t.div({ className: "form-row" }, [t.label({ for: "password" }, vm.i18n`Password`), password]), - t.div({ className: "form-row" }, [t.label({ for: "homeserver" }, vm.i18n`Homeserver`), homeserver]), t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), t.div({ className: "button-row" }, [ t.a({