blob: 4381a59707129d5ab9c443b3d103e34e847610f2 [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 '@polymer/iron-input/iron-input';
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-watched-projects-editor_html';
import {customElement, property} from '@polymer/decorators';
import {
AutocompleteQuery,
GrAutocomplete,
AutocompleteSuggestion,
} from '../../shared/gr-autocomplete/gr-autocomplete';
import {hasOwnProperty} from '../../../utils/common-util';
import {ProjectWatchInfo} from '../../../types/common';
import {appContext} from '../../../services/app-context';
import {IronInputElement} from '@polymer/iron-input';
const NOTIFICATION_TYPES = [
{name: 'Changes', key: 'notify_new_changes'},
{name: 'Patches', key: 'notify_new_patch_sets'},
{name: 'Comments', key: 'notify_all_comments'},
{name: 'Submits', key: 'notify_submitted_changes'},
{name: 'Abandons', key: 'notify_abandoned_changes'},
];
export interface GrWatchedProjectsEditor {
$: {
newFilter: HTMLInputElement;
newFilterInput: IronInputElement;
newProject: GrAutocomplete;
};
}
@customElement('gr-watched-projects-editor')
export class GrWatchedProjectsEditor extends PolymerElement {
static get template() {
return htmlTemplate;
}
@property({type: Boolean, notify: true})
hasUnsavedChanges = false;
@property({type: Array})
_projects?: ProjectWatchInfo[];
@property({type: Array})
_projectsToRemove: ProjectWatchInfo[] = [];
@property({type: Object})
_query: AutocompleteQuery;
private readonly restApiService = appContext.restApiService;
constructor() {
super();
this._query = input => this._getProjectSuggestions(input);
}
loadData() {
return this.restApiService.getWatchedProjects().then(projs => {
this._projects = projs;
});
}
save() {
let deletePromise: Promise<Response | undefined>;
if (this._projectsToRemove.length) {
deletePromise = this.restApiService.deleteWatchedProjects(
this._projectsToRemove
);
} else {
deletePromise = Promise.resolve(undefined);
}
return deletePromise
.then(() => {
if (this._projects) {
return this.restApiService.saveWatchedProjects(this._projects);
} else {
return Promise.resolve(undefined);
}
})
.then(projects => {
this._projects = projects;
this._projectsToRemove = [];
this.hasUnsavedChanges = false;
});
}
_getTypes() {
return NOTIFICATION_TYPES;
}
_getTypeCount() {
return this._getTypes().length;
}
_computeCheckboxChecked(project: ProjectWatchInfo, key: string) {
return hasOwnProperty(project, key);
}
_getProjectSuggestions(input: string) {
return this.restApiService.getSuggestedProjects(input).then(response => {
const projects: AutocompleteSuggestion[] = [];
for (const [name, project] of Object.entries(response ?? {})) {
projects.push({name, value: project.id});
}
return projects;
});
}
_handleRemoveProject(e: Event) {
const el = (dom(e) as EventApi).localTarget as HTMLInputElement;
const dataIndex = el.getAttribute('data-index');
if (dataIndex === null || !this._projects) return;
const index = Number(dataIndex);
const project = this._projects[index];
this.splice('_projects', index, 1);
this.push('_projectsToRemove', project);
this.hasUnsavedChanges = true;
}
_canAddProject(
project: string | null,
text: string | null,
filter: string | null
) {
if (project === null && text === null) {
return false;
}
// This will only be used if not using the auto complete
if (!project && text) {
return true;
}
if (!this._projects) return true;
// Check if the project with filter is already in the list.
for (let i = 0; i < this._projects.length; i++) {
if (
this._projects[i].project === project &&
this.areFiltersEqual(this._projects[i].filter, filter)
) {
return false;
}
}
return true;
}
_getNewProjectIndex(name: string, filter: string | null) {
if (!this._projects) return;
let i;
for (i = 0; i < this._projects.length; i++) {
const projectFilter = this._projects[i].filter;
if (
this._projects[i].project > name ||
(this._projects[i].project === name &&
this.isFilterDefined(projectFilter) &&
this.isFilterDefined(filter) &&
projectFilter! > filter!)
) {
break;
}
}
return i;
}
_handleAddProject() {
const newProject = this.$.newProject.value;
const newProjectName = this.$.newProject.text;
const filter = this.$.newFilter.value || null;
if (!this._canAddProject(newProject, newProjectName, filter)) {
return;
}
const insertIndex = this._getNewProjectIndex(newProjectName, filter);
if (insertIndex !== undefined) {
this.splice('_projects', insertIndex, 0, {
project: newProjectName,
filter,
_is_local: true,
});
}
this.$.newProject.clear();
this.$.newFilter.value = '';
this.hasUnsavedChanges = true;
}
_handleCheckboxChange(e: Event) {
const el = (dom(e) as EventApi).localTarget as HTMLInputElement;
if (el === null) return;
const dataIndex = el.getAttribute('data-index');
const key = el.getAttribute('data-key');
if (dataIndex === null || key === null) return;
const index = Number(dataIndex);
const checked = el.checked;
this.set(['_projects', index, key], !!checked);
this.hasUnsavedChanges = true;
}
_handleNotifCellClick(e: Event) {
if (e.target === null) return;
const checkbox = (e.target as HTMLElement).querySelector('input');
if (checkbox) {
checkbox.click();
}
}
isFilterDefined(filter: string | null | undefined) {
return filter !== null && filter !== undefined;
}
areFiltersEqual(
filter1: string | null | undefined,
filter2: string | null | undefined
) {
// null and undefined are equal
if (!this.isFilterDefined(filter1) && !this.isFilterDefined(filter2)) {
return true;
}
return filter1 === filter2;
}
}
declare global {
interface HTMLElementTagNameMap {
'gr-watched-projects-editor': GrWatchedProjectsEditor;
}
}