| /** |
| * @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', |
| 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' : ''; |
| }, |
| }); |
| })(); |