gristlabs_grist-core/app/client/components/ProfileForm.js
Paul Fitzpatrick 1654a2681f (core) move client code to core
Summary:
This moves all client code to core, and makes minimal fix-ups to
get grist and grist-core to compile correctly.  The client works
in core, but I'm leaving clean-up around the build and bundles to
follow-up.

Test Plan: existing tests pass; server-dev bundle looks sane

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2627
2020-10-02 13:24:21 -04:00

161 lines
5.4 KiB
JavaScript

/* global document */
// External dependencies
const _ = require('underscore');
const ko = require('knockout');
const BackboneEvents = require('backbone').Events;
// Grist client libs
const dispose = require('../lib/dispose');
const dom = require('../lib/dom');
const kf = require('../lib/koForm');
const kd = require('../lib/koDom');
const ModalDialog = require('./ModalDialog');
/**
* ProfileForm - Handles dom and settings for the profile box.
* @param {Login} login - The login instance.
*/
function ProfileForm(login) {
this._login = login;
this._comm = this._login.comm;
this._gristLogin = this._login.gristLogin;
this._errorNotify = ko.observable();
this._successNotify = ko.observable();
// Form data which may be filled in when modifying profile information.
this._newName = ko.observable('');
// Counter used to provide each edit profile sub-form with an id which indicates
// when it is visible.
this._formId = 1;
this._editingId = ko.observable(null);
this._profileDialog = this.autoDispose(ModalDialog.create({
title: 'User profile',
body: this._buildProfileDom(),
width: '420px'
}));
this._profileDialog.show();
// TODO: Some indication is necessary that verification is occurring between
// submitting the form and waiting for the box to close.
this.listenTo(this._comm, 'clientLogout', () => this.dispose());
this.listenTo(this._profileDialog, 'close', () => this.dispose());
}
_.extend(ProfileForm.prototype, BackboneEvents);
dispose.makeDisposable(ProfileForm);
/**
* Builds the body of the profile modal form.
*/
ProfileForm.prototype._buildProfileDom = function() {
return dom('div.profile-form',
// Email
// TODO: Allow changing email
this._buildProfileRow('Email', {
buildDisplayFunc: () => dom('div',
kd.text(this._login.emailObs),
dom.testId('ProfileForm_viewEmail')
)
}),
// Name
this._buildProfileRow('Name', {
buildDisplayFunc: () => dom('div',
kd.text(this._login.nameObs),
dom.testId('ProfileForm_viewName')
),
buildEditFunc: () => dom('div',
kf.label('New name'),
kf.text(this._newName, {}, dom.testId('ProfileForm_newName'))
),
submitFunc: () => this._submitNameChange()
}),
// TODO: Allow editing profile image.
kd.maybe(this._successNotify, success => {
return dom('div.login-success-notify',
dom('div.login-success-text', success)
);
}),
kd.maybe(this._errorNotify, err => {
return dom('div.login-error-notify',
dom('div.login-error-text', err)
);
})
);
};
/**
* Builds a row of the profile form.
* @param {String} label - Indicates the profile item displayed by the row.
* @param {Function} options.buildDisplayFunc - A function which returns dom representing
* the value of the profile item to be displayed. If omitted, no value is visible.
* @param {Function} options.buildEditFunc - A function which returns dom to change the
* value of the profile item. If omitted, the profile item may not be edited.
* @param {Function} options.submitFunc - A function to call to save changes to the
* profile item. MUST be included if buildEditFunc is included.
*/
ProfileForm.prototype._buildProfileRow = function(label, options) {
options = options || {};
let formId = this._formId++;
return dom('div.profile-row',
kf.row(
2, kf.label(label),
5, options.buildDisplayFunc ? options.buildDisplayFunc() : '',
1, dom('div.btn.edit-profile.glyphicon.glyphicon-pencil',
{ style: `visibility: ${options.buildEditFunc ? 'visible' : 'hidden'}` },
dom.testId(`ProfileForm_edit${label}`),
dom.on('click', () => {
this._editingId(this._editingId() === formId ? null : formId);
})
)
),
kd.maybe(() => this._editingId() === formId, () => {
return dom('div',
dom.on('keydown', e => {
if (e.keyCode === 13) {
// Current element is likely a knockout text field with changes that haven't yet been
// saved to the observable. Blur the current element to ensure its value is saved.
document.activeElement.blur();
options.submitFunc();
}
}),
dom('div.edit-profile-form',
options.buildEditFunc(),
dom('div.login-btns.flexhbox',
kf.buttonGroup(
kf.button(() => this._editingId(null), 'Cancel',
dom.testId('ProfileForm_cancel'))
),
kf.buttonGroup(
kf.accentButton(() => options.submitFunc(), 'Submit',
dom.testId('ProfileForm_submit'))
)
)
)
);
})
);
};
// Submits the profile name change form.
ProfileForm.prototype._submitNameChange = function() {
if (!this._newName()) {
throw new Error('Name may not be blank.');
}
return this._login.setProfileItem('name', this._newName())
// TODO: attemptRefreshToken() should be handled in a general way for all methods
// which require using tokens after sign in.
.then(() => {
this._editingId(null);
this._successNotify('Successfully changed name.');
})
.catch(err => {
console.error('Error changing name', err);
this._errorNotify(err.message);
});
};
module.exports = ProfileForm;