blob: 2658bd2b677fb079af5aea100411f7ccf8a6abfb [file] [log] [blame]
/**
* @license
* Copyright (C) 2017 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.
*/
(function() {
'use strict';
Polymer({
is: 'gr-edit-controls',
_legacyUndefinedCheck: true,
properties: {
change: Object,
patchNum: String,
/**
* TODO(kaspern): by default, the RESTORE action should be hidden in the
* file-list as it is a per-file action only. Remove this default value
* when the Actions dictionary is moved to a shared constants file and
* use the hiddenActions property in the parent component.
*/
hiddenActions: {
type: Array,
value() { return [GrEditConstants.Actions.RESTORE.id]; },
},
_actions: {
type: Array,
value() { return Object.values(GrEditConstants.Actions); },
},
_path: {
type: String,
value: '',
},
_newPath: {
type: String,
value: '',
},
_query: {
type: Function,
value() {
return this._queryFiles.bind(this);
},
},
},
behaviors: [
Gerrit.PatchSetBehavior,
],
_handleTap(e) {
e.preventDefault();
const action = Polymer.dom(e).localTarget.id;
switch (action) {
case GrEditConstants.Actions.OPEN.id:
this.openOpenDialog();
return;
case GrEditConstants.Actions.DELETE.id:
this.openDeleteDialog();
return;
case GrEditConstants.Actions.RENAME.id:
this.openRenameDialog();
return;
case GrEditConstants.Actions.RESTORE.id:
this.openRestoreDialog();
return;
}
},
/**
* @param {string=} opt_path
*/
openOpenDialog(opt_path) {
if (opt_path) { this._path = opt_path; }
return this._showDialog(this.$.openDialog);
},
/**
* @param {string=} opt_path
*/
openDeleteDialog(opt_path) {
if (opt_path) { this._path = opt_path; }
return this._showDialog(this.$.deleteDialog);
},
/**
* @param {string=} opt_path
*/
openRenameDialog(opt_path) {
if (opt_path) { this._path = opt_path; }
return this._showDialog(this.$.renameDialog);
},
/**
* @param {string=} opt_path
*/
openRestoreDialog(opt_path) {
if (opt_path) { this._path = opt_path; }
return this._showDialog(this.$.restoreDialog);
},
/**
* Given a path string, checks that it is a valid file path.
* @param {string} path
* @return {boolean}
*/
_isValidPath(path) {
// Double negation needed for strict boolean return type.
return !!path.length && !path.endsWith('/');
},
_computeRenameDisabled(path, newPath) {
return this._isValidPath(path) && this._isValidPath(newPath);
},
/**
* Given a dom event, gets the dialog that lies along this event path.
* @param {!Event} e
* @return {!Element|undefined}
*/
_getDialogFromEvent(e) {
return Polymer.dom(e).path.find(element => {
if (!element.classList) { return false; }
return element.classList.contains('dialog');
});
},
_showDialog(dialog) {
// Some dialogs may not fire their on-close event when closed in certain
// ways (e.g. by clicking outside the dialog body). This call prevents
// multiple dialogs from being shown in the same overlay.
this._hideAllDialogs();
return this.$.overlay.open().then(() => {
dialog.classList.toggle('invisible', false);
const autocomplete = dialog.querySelector('gr-autocomplete');
if (autocomplete) { autocomplete.focus(); }
this.async(() => { this.$.overlay.center(); }, 1);
});
},
_hideAllDialogs() {
const dialogs = Polymer.dom(this.root).querySelectorAll('.dialog');
for (const dialog of dialogs) { this._closeDialog(dialog); }
},
/**
* @param {Element|undefined} dialog
* @param {boolean=} clearInputs
*/
_closeDialog(dialog, clearInputs) {
if (!dialog) { return; }
if (clearInputs) {
// Dialog may have autocompletes and plain inputs -- as these have
// different properties representing their bound text, it is easier to
// just make two separate queries.
dialog.querySelectorAll('gr-autocomplete')
.forEach(input => { input.text = ''; });
dialog.querySelectorAll('input')
.forEach(input => { input.bindValue = ''; });
}
dialog.classList.toggle('invisible', true);
return this.$.overlay.close();
},
_handleDialogCancel(e) {
this._closeDialog(this._getDialogFromEvent(e));
},
_handleOpenConfirm(e) {
const url = Gerrit.Nav.getEditUrlForDiff(this.change, this._path,
this.patchNum);
Gerrit.Nav.navigateToRelativeUrl(url);
this._closeDialog(this._getDialogFromEvent(e), true);
},
_handleDeleteConfirm(e) {
this.$.restAPI.deleteFileInChangeEdit(this.change._number, this._path)
.then(res => {
if (!res.ok) { return; }
this._closeDialog(this._getDialogFromEvent(e), true);
Gerrit.Nav.navigateToChange(this.change);
});
},
_handleRestoreConfirm(e) {
this.$.restAPI.restoreFileInChangeEdit(this.change._number, this._path)
.then(res => {
if (!res.ok) { return; }
this._closeDialog(this._getDialogFromEvent(e), true);
Gerrit.Nav.navigateToChange(this.change);
});
},
_handleRenameConfirm(e) {
return this.$.restAPI.renameFileInChangeEdit(this.change._number,
this._path, this._newPath).then(res => {
if (!res.ok) { return; }
this._closeDialog(this._getDialogFromEvent(e), true);
Gerrit.Nav.navigateToChange(this.change);
});
},
_queryFiles(input) {
return this.$.restAPI.queryChangeFiles(this.change._number,
this.patchNum, input).then(res => res.map(file => {
return {name: file};
}));
},
_computeIsInvisible(id, hiddenActions) {
return hiddenActions.includes(id) ? 'invisible' : '';
},
});
})();