
getVisibleColumns method is not checking for any visibility at all but is simply renaming a clone of the parameter from "Project" to "Repo". Change-Id: I13c3dd5694037ad040444293e480adace76af24f
556 lines
15 KiB
TypeScript
556 lines
15 KiB
TypeScript
/**
|
|
* @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 '@polymer/iron-input/iron-input';
|
|
import '@polymer/paper-toggle-button/paper-toggle-button';
|
|
import '../../../styles/gr-form-styles';
|
|
import '../../../styles/gr-menu-page-styles';
|
|
import '../../../styles/gr-page-nav-styles';
|
|
import '../../../styles/shared-styles';
|
|
import {
|
|
applyTheme as applyDarkTheme,
|
|
removeTheme as removeDarkTheme,
|
|
} from '../../../styles/themes/dark-theme';
|
|
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
|
|
import '../gr-change-table-editor/gr-change-table-editor';
|
|
import '../../shared/gr-button/gr-button';
|
|
import '../../shared/gr-date-formatter/gr-date-formatter';
|
|
import '../../shared/gr-diff-preferences/gr-diff-preferences';
|
|
import '../../shared/gr-page-nav/gr-page-nav';
|
|
import '../../shared/gr-select/gr-select';
|
|
import '../gr-account-info/gr-account-info';
|
|
import '../gr-agreements-list/gr-agreements-list';
|
|
import '../gr-edit-preferences/gr-edit-preferences';
|
|
import '../gr-email-editor/gr-email-editor';
|
|
import '../gr-gpg-editor/gr-gpg-editor';
|
|
import '../gr-group-list/gr-group-list';
|
|
import '../gr-http-password/gr-http-password';
|
|
import '../gr-identities/gr-identities';
|
|
import '../gr-menu-editor/gr-menu-editor';
|
|
import '../gr-ssh-editor/gr-ssh-editor';
|
|
import '../gr-watched-projects-editor/gr-watched-projects-editor';
|
|
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
|
|
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
|
|
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
|
import {htmlTemplate} from './gr-settings-view_html';
|
|
import {getDocsBaseUrl} from '../../../utils/url-util';
|
|
import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
|
|
import {customElement, property, observe} from '@polymer/decorators';
|
|
import {AppElementParams} from '../../gr-app-types';
|
|
import {GrAccountInfo} from '../gr-account-info/gr-account-info';
|
|
import {GrWatchedProjectsEditor} from '../gr-watched-projects-editor/gr-watched-projects-editor';
|
|
import {GrGroupList} from '../gr-group-list/gr-group-list';
|
|
import {GrIdentities} from '../gr-identities/gr-identities';
|
|
import {GrEditPreferences} from '../gr-edit-preferences/gr-edit-preferences';
|
|
import {GrDiffPreferences} from '../../shared/gr-diff-preferences/gr-diff-preferences';
|
|
import {
|
|
PreferencesInput,
|
|
ServerInfo,
|
|
TopMenuItemInfo,
|
|
} from '../../../types/common';
|
|
import {GrSshEditor} from '../gr-ssh-editor/gr-ssh-editor';
|
|
import {GrGpgEditor} from '../gr-gpg-editor/gr-gpg-editor';
|
|
import {GrEmailEditor} from '../gr-email-editor/gr-email-editor';
|
|
import {CustomKeyboardEvent} from '../../../types/events';
|
|
import {fireAlert, fireTitleChange} from '../../../utils/event-util';
|
|
import {appContext} from '../../../services/app-context';
|
|
import {GerritView} from '../../../services/router/router-model';
|
|
|
|
const PREFS_SECTION_FIELDS: Array<keyof PreferencesInput> = [
|
|
'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 HTTP_AUTH = ['HTTP', 'HTTP_LDAP'];
|
|
|
|
enum CopyPrefsDirection {
|
|
PrefsToLocalPrefs,
|
|
LocalPrefsToPrefs,
|
|
}
|
|
|
|
type LocalMenuItemInfo = Omit<TopMenuItemInfo, 'id'>;
|
|
|
|
export interface GrSettingsView {
|
|
$: {
|
|
accountInfo: GrAccountInfo;
|
|
watchedProjectsEditor: GrWatchedProjectsEditor;
|
|
groupList: GrGroupList;
|
|
identities: GrIdentities;
|
|
editPrefs: GrEditPreferences;
|
|
diffPrefs: GrDiffPreferences;
|
|
sshEditor: GrSshEditor;
|
|
gpgEditor: GrGpgEditor;
|
|
emailEditor: GrEmailEditor;
|
|
insertSignedOff: HTMLInputElement;
|
|
workInProgressByDefault: HTMLInputElement;
|
|
showSizeBarsInFileList: HTMLInputElement;
|
|
publishCommentsOnPush: HTMLInputElement;
|
|
relativeDateInChangeTable: HTMLInputElement;
|
|
};
|
|
}
|
|
|
|
@customElement('gr-settings-view')
|
|
export class GrSettingsView extends ChangeTableMixin(
|
|
GestureEventListeners(LegacyElementMixin(PolymerElement))
|
|
) {
|
|
static get template() {
|
|
return htmlTemplate;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
|
|
@property({type: Object})
|
|
prefs: PreferencesInput = {};
|
|
|
|
@property({type: Object})
|
|
params?: AppElementParams;
|
|
|
|
@property({type: Boolean})
|
|
_accountInfoChanged?: boolean;
|
|
|
|
@property({type: Array})
|
|
_changeTableColumnsNotDisplayed?: string[];
|
|
|
|
@property({type: Object})
|
|
_localPrefs: PreferencesInput = {};
|
|
|
|
@property({type: Array})
|
|
_localChangeTableColumns: string[] = [];
|
|
|
|
@property({type: Array})
|
|
_localMenu: LocalMenuItemInfo[] = [];
|
|
|
|
@property({type: Boolean})
|
|
_loading = true;
|
|
|
|
@property({type: Boolean})
|
|
_changeTableChanged = false;
|
|
|
|
@property({type: Boolean})
|
|
_prefsChanged = false;
|
|
|
|
@property({type: Boolean})
|
|
_diffPrefsChanged?: boolean;
|
|
|
|
@property({type: Boolean})
|
|
_editPrefsChanged?: boolean;
|
|
|
|
@property({type: Boolean})
|
|
_menuChanged = false;
|
|
|
|
@property({type: Boolean})
|
|
_watchedProjectsChanged = false;
|
|
|
|
@property({type: Boolean})
|
|
_keysChanged = false;
|
|
|
|
@property({type: Boolean})
|
|
_gpgKeysChanged = false;
|
|
|
|
@property({type: String})
|
|
_newEmail?: string;
|
|
|
|
@property({type: Boolean})
|
|
_addingEmail = false;
|
|
|
|
@property({type: String})
|
|
_lastSentVerificationEmail?: string | null = null;
|
|
|
|
@property({type: Object})
|
|
_serverConfig?: ServerInfo;
|
|
|
|
@property({type: String})
|
|
_docsBaseUrl?: string | null;
|
|
|
|
@property({type: Boolean})
|
|
_emailsChanged?: boolean;
|
|
|
|
@property({type: Boolean})
|
|
_showNumber?: boolean;
|
|
|
|
@property({type: Boolean})
|
|
_isDark = false;
|
|
|
|
public _testOnly_loadingPromise?: Promise<void>;
|
|
|
|
private readonly restApiService = appContext.restApiService;
|
|
|
|
/** @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');
|
|
fireTitleChange(this, 'Settings');
|
|
|
|
this._isDark = !!window.localStorage.getItem('dark-theme');
|
|
|
|
const promises: Array<Promise<unknown>> = [
|
|
this.$.accountInfo.loadData(),
|
|
this.$.watchedProjectsEditor.loadData(),
|
|
this.$.groupList.loadData(),
|
|
this.$.identities.loadData(),
|
|
this.$.editPrefs.loadData(),
|
|
this.$.diffPrefs.loadData(),
|
|
];
|
|
|
|
promises.push(
|
|
this.restApiService.getPreferences().then(prefs => {
|
|
if (!prefs) {
|
|
throw new Error('getPreferences returned undefined');
|
|
}
|
|
this.prefs = prefs;
|
|
this._showNumber = !!prefs.legacycid_in_change_table;
|
|
this._copyPrefs(CopyPrefsDirection.PrefsToLocalPrefs);
|
|
this._localMenu = this._cloneMenu(prefs.my);
|
|
this._cloneChangeTableColumns(prefs.change_table);
|
|
})
|
|
);
|
|
|
|
promises.push(
|
|
this.restApiService.getConfig().then(config => {
|
|
this._serverConfig = config;
|
|
const configPromises: Array<Promise<void>> = [];
|
|
|
|
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(
|
|
getDocsBaseUrl(config, this.restApiService).then(baseUrl => {
|
|
this._docsBaseUrl = baseUrl;
|
|
})
|
|
);
|
|
|
|
return Promise.all(configPromises);
|
|
})
|
|
);
|
|
|
|
if (
|
|
this.params &&
|
|
this.params.view === GerritView.SETTINGS &&
|
|
this.params.emailToken
|
|
) {
|
|
promises.push(
|
|
this.restApiService
|
|
.confirmEmail(this.params.emailToken)
|
|
.then(message => {
|
|
if (message) {
|
|
fireAlert(this, message);
|
|
}
|
|
this.$.emailEditor.loadData();
|
|
})
|
|
);
|
|
} else {
|
|
promises.push(this.$.emailEditor.loadData());
|
|
}
|
|
|
|
this._testOnly_loadingPromise = Promise.all(promises).then(() => {
|
|
this._loading = false;
|
|
|
|
// Handle anchor tag for initial load
|
|
this._handleLocationChange();
|
|
});
|
|
}
|
|
|
|
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(direction: CopyPrefsDirection) {
|
|
let to;
|
|
let from;
|
|
if (direction === CopyPrefsDirection.LocalPrefsToPrefs) {
|
|
from = this._localPrefs;
|
|
to = 'prefs';
|
|
} else {
|
|
from = this.prefs;
|
|
to = '_localPrefs';
|
|
}
|
|
for (let i = 0; i < PREFS_SECTION_FIELDS.length; i++) {
|
|
this.set([to, PREFS_SECTION_FIELDS[i]], from[PREFS_SECTION_FIELDS[i]]);
|
|
}
|
|
}
|
|
|
|
_cloneMenu(prefs: TopMenuItemInfo[]) {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
return prefs.map(({id, ...item}) => {
|
|
return item;
|
|
});
|
|
}
|
|
|
|
_cloneChangeTableColumns(changeTable: string[]) {
|
|
let columns = this.renameProjectToRepoColumn(changeTable);
|
|
|
|
if (columns.length === 0) {
|
|
columns = this.columnNames;
|
|
this._changeTableColumnsNotDisplayed = [];
|
|
} else {
|
|
this._changeTableColumnsNotDisplayed = this.getComplementColumns(
|
|
changeTable
|
|
);
|
|
}
|
|
this._localChangeTableColumns = columns;
|
|
}
|
|
|
|
@observe('_localChangeTableColumns', '_showNumber')
|
|
_handleChangeTableChanged() {
|
|
if (this._isLoading()) {
|
|
return;
|
|
}
|
|
this._changeTableChanged = true;
|
|
}
|
|
|
|
@observe('_localPrefs.*')
|
|
_handlePrefsChanged() {
|
|
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);
|
|
}
|
|
|
|
@observe('_localMenu.splices')
|
|
_handleMenuChanged() {
|
|
if (this._isLoading()) {
|
|
return;
|
|
}
|
|
this._menuChanged = true;
|
|
}
|
|
|
|
_handleSaveAccountInfo() {
|
|
this.$.accountInfo.save();
|
|
}
|
|
|
|
_handleSavePreferences() {
|
|
this._copyPrefs(CopyPrefsDirection.LocalPrefsToPrefs);
|
|
|
|
return this.restApiService.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(this._localChangeTableColumns);
|
|
return this.restApiService.savePreferences(this.prefs).then(() => {
|
|
this._changeTableChanged = false;
|
|
});
|
|
}
|
|
|
|
_handleSaveDiffPreferences() {
|
|
this.$.diffPrefs.save();
|
|
}
|
|
|
|
_handleSaveEditPreferences() {
|
|
this.$.editPrefs.save();
|
|
}
|
|
|
|
_handleSaveMenu() {
|
|
this.set('prefs.my', this._localMenu);
|
|
return this.restApiService.savePreferences(this.prefs).then(() => {
|
|
this._menuChanged = false;
|
|
});
|
|
}
|
|
|
|
_handleResetMenuButton() {
|
|
return this.restApiService.getDefaultPreferences().then(data => {
|
|
if (data?.my) {
|
|
this._localMenu = this._cloneMenu(data.my);
|
|
}
|
|
});
|
|
}
|
|
|
|
_handleSaveWatchedProjects() {
|
|
this.$.watchedProjectsEditor.save();
|
|
}
|
|
|
|
_computeHeaderClass(changed?: boolean) {
|
|
return changed ? 'edited' : '';
|
|
}
|
|
|
|
_handleSaveEmails() {
|
|
this.$.emailEditor.save();
|
|
}
|
|
|
|
_handleNewEmailKeydown(e: CustomKeyboardEvent) {
|
|
if (e.keyCode === 13) {
|
|
// Enter
|
|
e.stopPropagation();
|
|
this._handleAddEmailButton();
|
|
}
|
|
}
|
|
|
|
_isNewEmailValid(newEmail?: string): newEmail is string {
|
|
return !!newEmail && newEmail.includes('@');
|
|
}
|
|
|
|
_computeAddEmailButtonEnabled(newEmail?: string, addingEmail?: boolean) {
|
|
return this._isNewEmailValid(newEmail) && !addingEmail;
|
|
}
|
|
|
|
_handleAddEmailButton() {
|
|
if (!this._isNewEmailValid(this._newEmail)) return;
|
|
|
|
this._addingEmail = true;
|
|
this.restApiService.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?: string) {
|
|
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');
|
|
removeDarkTheme();
|
|
} else {
|
|
window.localStorage.setItem('dark-theme', 'true');
|
|
applyDarkTheme();
|
|
}
|
|
this._isDark = !!window.localStorage.getItem('dark-theme');
|
|
fireAlert(this, `Theme changed to ${this._isDark ? 'dark' : 'light'}.`);
|
|
}
|
|
|
|
_showHttpAuth(config?: ServerInfo) {
|
|
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: Event) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'gr-settings-view': GrSettingsView;
|
|
}
|
|
}
|