blob: 158c5eb0aac8dbf29dcbd83bd83dc1d5f97d0ca5 [file] [log] [blame]
/**
* @license
* Copyright (C) 2016 The Android Open Source Project
*
* 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 '../../../scripts/bundled-polymer.js';
import '@polymer/iron-input/iron-input.js';
import '@polymer/paper-toggle-button/paper-toggle-button.js';
import '../../../styles/gr-form-styles.js';
import '../../../styles/gr-menu-page-styles.js';
import '../../../styles/gr-page-nav-styles.js';
import '../../../styles/shared-styles.js';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
import '../gr-change-table-editor/gr-change-table-editor.js';
import '../../shared/gr-button/gr-button.js';
import '../../shared/gr-date-formatter/gr-date-formatter.js';
import '../../shared/gr-diff-preferences/gr-diff-preferences.js';
import '../../shared/gr-page-nav/gr-page-nav.js';
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
import '../../shared/gr-select/gr-select.js';
import '../gr-account-info/gr-account-info.js';
import '../gr-agreements-list/gr-agreements-list.js';
import '../gr-edit-preferences/gr-edit-preferences.js';
import '../gr-email-editor/gr-email-editor.js';
import '../gr-gpg-editor/gr-gpg-editor.js';
import '../gr-group-list/gr-group-list.js';
import '../gr-http-password/gr-http-password.js';
import '../gr-identities/gr-identities.js';
import '../gr-menu-editor/gr-menu-editor.js';
import '../gr-ssh-editor/gr-ssh-editor.js';
import '../gr-watched-projects-editor/gr-watched-projects-editor.js';
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-settings-view_html.js';
import {DocsUrlBehavior} from '../../../behaviors/docs-url-behavior/docs-url-behavior.js';
import {ChangeTableBehavior} from '../../../behaviors/gr-change-table-behavior/gr-change-table-behavior.js';
const PREFS_SECTION_FIELDS = [
'changes_per_page',
'date_format',
'time_format',
'email_strategy',
'diff_view',
'publish_comments_on_push',
'work_in_progress_by_default',
'default_base_for_merges',
'signed_off_by',
'email_format',
'size_bar_in_change_table',
'relative_date_in_change_table',
];
const GERRIT_DOCS_BASE_URL = 'https://gerrit-review.googlesource.com/' +
'Documentation';
const GERRIT_DOCS_FILTER_PATH = '/user-notify.html';
const ABSOLUTE_URL_PATTERN = /^https?:/;
const TRAILING_SLASH_PATTERN = /\/$/;
const RELOAD_MESSAGE = 'Reloading...';
const HTTP_AUTH = [
'HTTP',
'HTTP_LDAP',
];
/**
* @extends Polymer.Element
*/
class GrSettingsView extends mixinBehaviors( [
DocsUrlBehavior,
ChangeTableBehavior,
], GestureEventListeners(
LegacyElementMixin(
PolymerElement))) {
static get template() { return htmlTemplate; }
static get is() { return 'gr-settings-view'; }
/**
* Fired when the title of the page should change.
*
* @event title-change
*/
/**
* Fired with email confirmation text, or when the page reloads.
*
* @event show-alert
*/
static get properties() {
return {
prefs: {
type: Object,
value() { return {}; },
},
params: {
type: Object,
value() { return {}; },
},
_accountInfoChanged: Boolean,
_changeTableColumnsNotDisplayed: Array,
/** @type {?} */
_localPrefs: {
type: Object,
value() { return {}; },
},
_localChangeTableColumns: {
type: Array,
value() { return []; },
},
_localMenu: {
type: Array,
value() { return []; },
},
_loading: {
type: Boolean,
value: true,
},
_changeTableChanged: {
type: Boolean,
value: false,
},
_prefsChanged: {
type: Boolean,
value: false,
},
/** @type {?} */
_diffPrefsChanged: Boolean,
/** @type {?} */
_editPrefsChanged: Boolean,
_menuChanged: {
type: Boolean,
value: false,
},
_watchedProjectsChanged: {
type: Boolean,
value: false,
},
_keysChanged: {
type: Boolean,
value: false,
},
_gpgKeysChanged: {
type: Boolean,
value: false,
},
_newEmail: String,
_addingEmail: {
type: Boolean,
value: false,
},
_lastSentVerificationEmail: {
type: String,
value: null,
},
/** @type {?} */
_serverConfig: Object,
/** @type {?string} */
_docsBaseUrl: String,
_emailsChanged: Boolean,
/**
* For testing purposes.
*/
_loadingPromise: Object,
_showNumber: Boolean,
_isDark: {
type: Boolean,
value: false,
},
};
}
static get observers() {
return [
'_handlePrefsChanged(_localPrefs.*)',
'_handleMenuChanged(_localMenu.splices)',
'_handleChangeTableChanged(_localChangeTableColumns, _showNumber)',
];
}
/** @override */
attached() {
super.attached();
// Polymer 2: anchor tag won't work on shadow DOM
// we need to manually calling scrollIntoView when hash changed
this.listen(window, 'location-change', '_handleLocationChange');
this.dispatchEvent(new CustomEvent('title-change', {
detail: {title: 'Settings'},
composed: true, bubbles: true,
}));
this._isDark = !!window.localStorage.getItem('dark-theme');
const promises = [
this.$.accountInfo.loadData(),
this.$.watchedProjectsEditor.loadData(),
this.$.groupList.loadData(),
this.$.identities.loadData(),
this.$.editPrefs.loadData(),
this.$.diffPrefs.loadData(),
];
promises.push(this.$.restAPI.getPreferences().then(prefs => {
this.prefs = prefs;
this._showNumber = !!prefs.legacycid_in_change_table;
this._copyPrefs('_localPrefs', 'prefs');
this._cloneMenu(prefs.my);
this._cloneChangeTableColumns();
}));
promises.push(this.$.restAPI.getConfig().then(config => {
this._serverConfig = config;
const configPromises = [];
if (this._serverConfig && this._serverConfig.sshd) {
configPromises.push(this.$.sshEditor.loadData());
}
if (this._serverConfig &&
this._serverConfig.receive &&
this._serverConfig.receive.enable_signed_push) {
configPromises.push(this.$.gpgEditor.loadData());
}
configPromises.push(
this.getDocsBaseUrl(config, this.$.restAPI)
.then(baseUrl => { this._docsBaseUrl = baseUrl; }));
return Promise.all(configPromises);
}));
if (this.params.emailToken) {
promises.push(this.$.restAPI.confirmEmail(this.params.emailToken).then(
message => {
if (message) {
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {message},
composed: true, bubbles: true,
}));
}
this.$.emailEditor.loadData();
}));
} else {
promises.push(this.$.emailEditor.loadData());
}
this._loadingPromise = Promise.all(promises).then(() => {
this._loading = false;
// Handle anchor tag for initial load
this._handleLocationChange();
});
}
/** @override */
detached() {
super.detached();
this.unlisten(window, 'location-change', '_handleLocationChange');
}
_handleLocationChange() {
// Handle anchor tag after dom attached
const urlHash = window.location.hash;
if (urlHash) {
// Use shadowRoot for Polymer 2
const elem = (this.shadowRoot || document).querySelector(urlHash);
if (elem) {
elem.scrollIntoView();
}
}
}
reloadAccountDetail() {
Promise.all([
this.$.accountInfo.loadData(),
this.$.emailEditor.loadData(),
]);
}
_isLoading() {
return this._loading || this._loading === undefined;
}
_copyPrefs(to, from) {
for (let i = 0; i < PREFS_SECTION_FIELDS.length; i++) {
this.set([to, PREFS_SECTION_FIELDS[i]],
this[from][PREFS_SECTION_FIELDS[i]]);
}
}
_cloneMenu(prefs) {
const menu = [];
for (const item of prefs) {
menu.push({
name: item.name,
url: item.url,
target: item.target,
});
}
this._localMenu = menu;
}
_cloneChangeTableColumns() {
let columns = this.getVisibleColumns(this.prefs.change_table);
if (columns.length === 0) {
columns = this.columnNames;
this._changeTableColumnsNotDisplayed = [];
} else {
this._changeTableColumnsNotDisplayed = this.getComplementColumns(
this.prefs.change_table);
}
this._localChangeTableColumns = columns;
}
_formatChangeTableColumns(changeTableArray) {
return changeTableArray.map(item => {
return {column: item};
});
}
_handleChangeTableChanged() {
if (this._isLoading()) { return; }
this._changeTableChanged = true;
}
_handlePrefsChanged(prefs) {
if (this._isLoading()) { return; }
this._prefsChanged = true;
}
_handleRelativeDateInChangeTable() {
this.set('_localPrefs.relative_date_in_change_table',
this.$.relativeDateInChangeTable.checked);
}
_handleShowSizeBarsInFileListChanged() {
this.set('_localPrefs.size_bar_in_change_table',
this.$.showSizeBarsInFileList.checked);
}
_handlePublishCommentsOnPushChanged() {
this.set('_localPrefs.publish_comments_on_push',
this.$.publishCommentsOnPush.checked);
}
_handleWorkInProgressByDefault() {
this.set('_localPrefs.work_in_progress_by_default',
this.$.workInProgressByDefault.checked);
}
_handleInsertSignedOff() {
this.set('_localPrefs.signed_off_by', this.$.insertSignedOff.checked);
}
_handleMenuChanged() {
if (this._isLoading()) { return; }
this._menuChanged = true;
}
_handleSaveAccountInfo() {
this.$.accountInfo.save();
}
_handleSavePreferences() {
this._copyPrefs('prefs', '_localPrefs');
return this.$.restAPI.savePreferences(this.prefs).then(() => {
this._prefsChanged = false;
});
}
_handleSaveChangeTable() {
this.set('prefs.change_table', this._localChangeTableColumns);
this.set('prefs.legacycid_in_change_table', this._showNumber);
this._cloneChangeTableColumns();
return this.$.restAPI.savePreferences(this.prefs).then(() => {
this._changeTableChanged = false;
});
}
_handleSaveDiffPreferences() {
this.$.diffPrefs.save();
}
_handleSaveEditPreferences() {
this.$.editPrefs.save();
}
_handleSaveMenu() {
this.set('prefs.my', this._localMenu);
this._cloneMenu(this.prefs.my);
return this.$.restAPI.savePreferences(this.prefs).then(() => {
this._menuChanged = false;
});
}
_handleResetMenuButton() {
return this.$.restAPI.getDefaultPreferences().then(data => {
if (data && data.my) {
this._cloneMenu(data.my);
}
});
}
_handleSaveWatchedProjects() {
this.$.watchedProjectsEditor.save();
}
_computeHeaderClass(changed) {
return changed ? 'edited' : '';
}
_handleSaveEmails() {
this.$.emailEditor.save();
}
_handleNewEmailKeydown(e) {
if (e.keyCode === 13) { // Enter
e.stopPropagation();
this._handleAddEmailButton();
}
}
_isNewEmailValid(newEmail) {
return newEmail && newEmail.includes('@');
}
_computeAddEmailButtonEnabled(newEmail, addingEmail) {
return this._isNewEmailValid(newEmail) && !addingEmail;
}
_handleAddEmailButton() {
if (!this._isNewEmailValid(this._newEmail)) { return; }
this._addingEmail = true;
this.$.restAPI.addAccountEmail(this._newEmail).then(response => {
this._addingEmail = false;
// If it was unsuccessful.
if (response.status < 200 || response.status >= 300) { return; }
this._lastSentVerificationEmail = this._newEmail;
this._newEmail = '';
});
}
_getFilterDocsLink(docsBaseUrl) {
let base = docsBaseUrl;
if (!base || !ABSOLUTE_URL_PATTERN.test(base)) {
base = GERRIT_DOCS_BASE_URL;
}
// Remove any trailing slash, since it is in the GERRIT_DOCS_FILTER_PATH.
base = base.replace(TRAILING_SLASH_PATTERN, '');
return base + GERRIT_DOCS_FILTER_PATH;
}
_handleToggleDark() {
if (this._isDark) {
window.localStorage.removeItem('dark-theme');
} else {
window.localStorage.setItem('dark-theme', 'true');
}
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {message: RELOAD_MESSAGE},
bubbles: true,
composed: true,
}));
this.async(() => {
window.location.reload();
}, 1);
}
_showHttpAuth(config) {
if (config && config.auth &&
config.auth.git_basic_auth_policy) {
return HTTP_AUTH.includes(
config.auth.git_basic_auth_policy.toUpperCase());
}
return false;
}
/**
* Work around a issue on iOS when clicking turns into double tap
*/
_onTapDarkToggle(e) {
e.preventDefault();
}
}
customElements.define(GrSettingsView.is, GrSettingsView);