aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx')
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx231
1 files changed, 231 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx b/server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx
new file mode 100644
index 00000000000..01b1d06f023
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/account/notifications/ProjectModal.tsx
@@ -0,0 +1,231 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as classNames from 'classnames';
+import { debounce } from 'lodash';
+import * as React from 'react';
+import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
+import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown';
+import SearchBox from 'sonar-ui-common/components/controls/SearchBox';
+import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { getSuggestions } from '../../../api/components';
+
+interface Props {
+ addedProjects: T.NotificationProject[];
+ closeModal: VoidFunction;
+ onSubmit: (project: T.NotificationProject) => void;
+}
+
+interface State {
+ highlighted?: T.NotificationProject;
+ loading?: boolean;
+ query?: string;
+ open?: boolean;
+ selectedProject?: T.NotificationProject;
+ suggestions?: T.NotificationProject[];
+}
+
+export default class ProjectModal extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {};
+ this.handleSearch = debounce(this.handleSearch, 250);
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleKeyDown = (event: React.KeyboardEvent) => {
+ switch (event.keyCode) {
+ case 13:
+ event.preventDefault();
+ this.handleSelectHighlighted();
+ break;
+ case 38:
+ event.preventDefault();
+ this.handleHighlightPrevious();
+ break;
+ case 40:
+ event.preventDefault();
+ this.handleHighlightNext();
+ break;
+ }
+ };
+
+ getCurrentIndex = () => {
+ const { highlighted, suggestions } = this.state;
+ return highlighted && suggestions
+ ? suggestions.findIndex(suggestion => suggestion.project === highlighted.project)
+ : -1;
+ };
+
+ highlightIndex = (index: number) => {
+ const { suggestions } = this.state;
+ if (suggestions && suggestions.length > 0) {
+ if (index < 0) {
+ index = suggestions.length - 1;
+ } else if (index >= suggestions.length) {
+ index = 0;
+ }
+ this.setState({
+ highlighted: suggestions[index]
+ });
+ }
+ };
+
+ handleHighlightPrevious = () => {
+ this.highlightIndex(this.getCurrentIndex() - 1);
+ };
+
+ handleHighlightNext = () => {
+ this.highlightIndex(this.getCurrentIndex() + 1);
+ };
+
+ handleSelectHighlighted = () => {
+ const { highlighted, selectedProject } = this.state;
+ if (highlighted !== undefined) {
+ if (selectedProject !== undefined && highlighted.project === selectedProject.project) {
+ this.handleSubmit();
+ } else {
+ this.handleSelect(highlighted);
+ }
+ }
+ };
+
+ handleSearch = (query: string) => {
+ const { addedProjects } = this.props;
+
+ if (query.length < 2) {
+ this.setState({ open: false, query });
+ return Promise.resolve([]);
+ }
+
+ this.setState({ loading: true, query });
+ return getSuggestions(query).then(
+ r => {
+ if (this.mounted) {
+ let suggestions = undefined;
+ const projects = r.results.find(domain => domain.q === 'TRK');
+ if (projects && projects.items.length > 0) {
+ suggestions = projects.items
+ .filter(item => !addedProjects.find(p => p.project === item.key))
+ .map(item => ({
+ project: item.key,
+ projectName: item.name
+ }));
+ }
+ this.setState({ loading: false, open: true, suggestions });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false, open: false });
+ }
+ }
+ );
+ };
+
+ handleSelect = (selectedProject: T.NotificationProject) => {
+ this.setState({
+ open: false,
+ query: selectedProject.projectName,
+ selectedProject
+ });
+ };
+
+ handleSubmit = () => {
+ const { selectedProject } = this.state;
+ if (selectedProject) {
+ this.props.onSubmit(selectedProject);
+ }
+ };
+
+ render() {
+ const { closeModal } = this.props;
+ const { highlighted, loading, query, open, selectedProject, suggestions } = this.state;
+ const header = translate('my_account.set_notifications_for.title');
+ return (
+ <SimpleModal header={header} onClose={closeModal} onSubmit={this.handleSubmit}>
+ {({ onCloseClick, onFormSubmit }) => (
+ <form onSubmit={onFormSubmit}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+ <div className="modal-body">
+ <div className="modal-field abs-width-400">
+ <label>{translate('my_account.set_notifications_for')}</label>
+ <SearchBox
+ autoFocus={true}
+ onChange={this.handleSearch}
+ onKeyDown={this.handleKeyDown}
+ placeholder={translate('search.placeholder')}
+ value={query}
+ />
+
+ {loading && <i className="spinner spacer-left" />}
+
+ {!loading && open && (
+ <div className="position-relative">
+ <DropdownOverlay className="abs-width-400" noPadding={true}>
+ {suggestions && suggestions.length > 0 ? (
+ <ul className="notifications-add-project-search-results">
+ {suggestions.map(suggestion => (
+ <li
+ className={classNames({
+ active: highlighted && highlighted.project === suggestion.project
+ })}
+ key={suggestion.project}
+ onClick={() => this.handleSelect(suggestion)}>
+ {suggestion.projectName}
+ </li>
+ ))}
+ </ul>
+ ) : (
+ <div className="notifications-add-project-no-search-results">
+ {translate('no_results')}
+ </div>
+ )}
+ </DropdownOverlay>
+ </div>
+ )}
+ </div>
+ </div>
+ <footer className="modal-foot">
+ <div>
+ <SubmitButton disabled={selectedProject === undefined}>
+ {translate('add_verb')}
+ </SubmitButton>
+ <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
+ </div>
+ </footer>
+ </form>
+ )}
+ </SimpleModal>
+ );
+ }
+}