diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2016-12-08 17:35:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-08 17:35:26 +0100 |
commit | 621544af2b072e21e42b8d4c1c2bd800cbfdd696 (patch) | |
tree | 271306721ea30ca9549a4649eae6059ed5aa4ffa /server/sonar-web/src/main/js/app | |
parent | fb9dd756d3e18058e13384b4119e220d3ae1a43d (diff) | |
download | sonarqube-621544af2b072e21e42b8d4c1c2bd800cbfdd696.tar.gz sonarqube-621544af2b072e21e42b8d4c1c2bd800cbfdd696.zip |
SONAR-8505 Implement smooth transition between pages (#1440)
Diffstat (limited to 'server/sonar-web/src/main/js/app')
12 files changed, 243 insertions, 81 deletions
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.js b/server/sonar-web/src/main/js/app/components/GlobalContainer.js index da833189128..32b5f620145 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.js +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.js @@ -25,11 +25,13 @@ import GlobalMessagesContainer from './GlobalMessagesContainer'; export default class GlobalContainer extends React.Component { render () { + // it is important to pass `location` down to `GlobalNav` to trigger render on url change + return ( <div className="global-container"> <div className="page-wrapper page-wrapper-global" id="container"> <div className="page-container"> - <GlobalNav/> + <GlobalNav location={this.props.location}/> <GlobalMessagesContainer/> {this.props.children} </div> diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.js b/server/sonar-web/src/main/js/app/components/GlobalFooter.js index e128246b69b..8d0e5472014 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooter.js +++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.js @@ -19,6 +19,7 @@ */ // @flow import React from 'react'; +import { Link } from 'react-router'; import { connect } from 'react-redux'; import { getAppState } from '../store/rootReducer'; @@ -64,9 +65,9 @@ class GlobalFooter extends React.Component { {' - '} <a href="http://redirect.sonarsource.com/doc/plugin-library.html">Plugins</a> {' - '} - <a href={window.baseUrl + '/web_api'}>Web API</a> + <Link to="/web_api">Web API</Link> {' - '} - <a href={window.baseUrl + '/about'}>About</a> + <Link to="/about">About</Link> </div> </div> ); diff --git a/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.js b/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.js index 0ee6c22edb5..6faf954d495 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.js +++ b/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.js @@ -20,9 +20,12 @@ import { connect } from 'react-redux'; import GlobalMessages from '../../components/controls/GlobalMessages'; import { getGlobalMessages } from '../store/rootReducer'; +import { closeGlobalMessage } from '../../components/store/globalMessages'; const mapStateToProps = state => ({ messages: getGlobalMessages(state) }); -export default connect(mapStateToProps)(GlobalMessages); +const mapDispatchToProps = { closeGlobalMessage }; + +export default connect(mapStateToProps, mapDispatchToProps)(GlobalMessages); diff --git a/server/sonar-web/src/main/js/app/components/NotFound.js b/server/sonar-web/src/main/js/app/components/NotFound.js index 16b40224cef..85279ed1a14 100644 --- a/server/sonar-web/src/main/js/app/components/NotFound.js +++ b/server/sonar-web/src/main/js/app/components/NotFound.js @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; +import { Link } from 'react-router'; import SimpleContainer from './SimpleContainer'; export default class NotFound extends React.Component { @@ -26,7 +27,7 @@ export default class NotFound extends React.Component { <SimpleContainer> <h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2> <p className="spacer-bottom">You may have mistyped the address or the page may have moved.</p> - <p><a href={window.baseUrl + '/'}>Go back to the homepage</a></p> + <p><Link to="/">Go back to the homepage</Link></p> </SimpleContainer> ); } diff --git a/server/sonar-web/src/main/js/app/components/ProjectContainer.js b/server/sonar-web/src/main/js/app/components/ProjectContainer.js index 1a83c88e618..44d64f0cd9e 100644 --- a/server/sonar-web/src/main/js/app/components/ProjectContainer.js +++ b/server/sonar-web/src/main/js/app/components/ProjectContainer.js @@ -33,8 +33,15 @@ class ProjectContainer extends React.Component { this.props.fetchProject(); } + componentDidUpdate (prevProps) { + if (prevProps.location.query.id !== this.props.location.query.id) { + this.props.fetchProject(); + } + } + render () { - if (!this.props.project) { + // check `canBeFavorite` to be sure that /api/navigation/component has been already called + if (!this.props.project || this.props.project.canBeFavorite == null) { return null; } diff --git a/server/sonar-web/src/main/js/app/components/SimpleContainer.js b/server/sonar-web/src/main/js/app/components/SimpleContainer.js index 17d9c64cb69..342bffa2bc3 100644 --- a/server/sonar-web/src/main/js/app/components/SimpleContainer.js +++ b/server/sonar-web/src/main/js/app/components/SimpleContainer.js @@ -23,7 +23,10 @@ import GlobalFooter from './GlobalFooter'; export default class SimpleContainer extends React.Component { static propTypes = { - children: React.PropTypes.element.isRequired + children: React.PropTypes.oneOfType([ + React.PropTypes.element, + React.PropTypes.arrayOf(React.PropTypes.element) + ]) }; componentDidMount () { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js index b6dcd1c7359..5e3057b65ff 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js @@ -37,7 +37,7 @@ export default React.createClass({ }, loadStatus() { - getTasksForComponent(this.props.component.uuid).then(r => { + getTasksForComponent(this.props.component.id).then(r => { this.setState({ isPending: !!_.findWhere(r.queue, { status: STATUSES.PENDING }), isInProgress: !!_.findWhere(r.queue, { status: STATUSES.IN_PROGRESS }), diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js index 52ed1e348a5..c348e186615 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js @@ -19,8 +19,8 @@ */ import classNames from 'classnames'; import React from 'react'; +import { Link } from 'react-router'; import { translate } from '../../../../helpers/l10n'; -import { getComponentUrl } from '../../../../helpers/urls'; const SETTINGS_URLS = [ '/project/settings', @@ -50,11 +50,6 @@ export default class ComponentNavMenu extends React.Component { return qualifier === 'VW' || qualifier === 'SVW'; } - isFixedDashboardActive () { - const path = window.location.pathname; - return path.indexOf(window.baseUrl + '/dashboard') === 0 || path.indexOf(window.baseUrl + '/governance') === 0; - } - shouldShowAdministration () { return Object.keys(this.props.conf).some(key => this.props.conf[key]); } @@ -73,12 +68,13 @@ export default class ComponentNavMenu extends React.Component { } renderDashboardLink () { - const url = getComponentUrl(this.props.component.key); - const name = <i className="icon-home"/>; - const className = classNames({ active: this.isFixedDashboardActive() }); return ( - <li key="overview" className={className}> - <a href={url}>{name}</a> + <li> + <Link + to={{ pathname: '/dashboard', query: { id: this.props.component.key } }} + activeClassName="active"> + <i className="icon-home"/> + </Link> </li> ); } @@ -88,19 +84,39 @@ export default class ComponentNavMenu extends React.Component { return null; } - const url = `/code/?id=${encodeURIComponent(this.props.component.key)}`; - const header = this.isView() ? translate('view_projects.page') : translate('code.page'); - return this.renderLink(url, header, '/code'); + return ( + <li> + <Link + to={{ pathname: '/code', query: { id: this.props.component.key } }} + activeClassName="active"> + {this.isView() ? translate('view_projects.page') : translate('code.page')} + </Link> + </li> + ); } renderComponentIssuesLink () { - const url = `/component_issues?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('issues.page'), '/component_issues'); + return ( + <li> + <Link + to={{ pathname: '/component_issues', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('issues.page')} + </Link> + </li> + ); } renderComponentMeasuresLink () { - const url = `/component_measures/?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('layout.measures'), '/component_measures'); + return ( + <li> + <Link + to={{ pathname: '/component_measures', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('layout.measures')} + </Link> + </li> + ); } renderAdministration () { @@ -136,48 +152,90 @@ export default class ComponentNavMenu extends React.Component { if (!this.props.conf.showSettings) { return null; } - const url = `/project/settings?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('project_settings.page'), '/project/settings'); + return ( + <li> + <Link + to={{ pathname: '/project/settings', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('project_settings.page')} + </Link> + </li> + ); } renderProfilesLink () { if (!this.props.conf.showQualityProfiles) { return null; } - const url = `/project/quality_profiles?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('project_quality_profiles.page'), '/project/quality_profiles'); + return ( + <li> + <Link + to={{ pathname: '/project/quality_profiles', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('project_quality_profiles.page')} + </Link> + </li> + ); } renderQualityGateLink () { if (!this.props.conf.showQualityGates) { return null; } - const url = `/project/quality_gate?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('project_quality_gate.page'), '/project/quality_gate'); + return ( + <li> + <Link + to={{ pathname: '/project/quality_gate', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('project_quality_gate.page')} + </Link> + </li> + ); } renderCustomMeasuresLink () { if (!this.props.conf.showManualMeasures) { return null; } - const url = `/custom_measures?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('custom_measures.page'), '/custom_measures'); + return ( + <li> + <Link + to={{ pathname: '/custom_measures', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('custom_measures.page')} + </Link> + </li> + ); } renderLinksLink () { if (!this.props.conf.showLinks) { return null; } - const url = `/project/links?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('project_links.page'), '/project/links'); + return ( + <li> + <Link + to={{ pathname: '/project/links', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('project_links.page')} + </Link> + </li> + ); } renderPermissionsLink () { if (!this.props.conf.showPermissions) { return null; } - const url = `/project_roles?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('permissions.page'), '/project_roles'); + return ( + <li> + <Link + to={{ pathname: '/project_roles', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('permissions.page')} + </Link> + </li> + ); } renderHistoryLink () { @@ -199,16 +257,30 @@ export default class ComponentNavMenu extends React.Component { if (!this.props.conf.showBackgroundTasks) { return null; } - const url = `/project/background_tasks?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('background_tasks.page'), '/project/background_tasks'); + return ( + <li> + <Link + to={{ pathname: '/project/background_tasks', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('background_tasks.page')} + </Link> + </li> + ); } renderUpdateKeyLink () { if (!this.props.conf.showUpdateKey) { return null; } - const url = `/project/key?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('update_key.page'), '/project/key'); + return ( + <li> + <Link + to={{ pathname: '/project/key', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('update_key.page')} + </Link> + </li> + ); } renderDeletionLink () { @@ -218,8 +290,15 @@ export default class ComponentNavMenu extends React.Component { return null; } - const url = `/project/deletion?id=${encodeURIComponent(this.props.component.key)}`; - return this.renderLink(url, translate('deletion.page'), '/project/deletion'); + return ( + <li> + <Link + to={{ pathname: '/project/deletion', query: { id: this.props.component.key } }} + activeClassName="active"> + {translate('deletion.page')} + </Link> + </li> + ); } renderExtensions () { diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js index 6542a9fb20b..b845eedfe58 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; +import { Link } from 'react-router'; import { connect } from 'react-redux'; import { getSettingValue, getCurrentUser } from '../../../store/rootReducer'; import { translate } from '../../../../helpers/l10n'; @@ -40,11 +41,10 @@ class GlobalNavBranding extends React.Component { render () { const homeController = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/about'; - const homeUrl = window.baseUrl + homeController; const homeLinkClassName = 'navbar-brand' + (this.props.customLogoUrl ? ' navbar-brand-custom' : ''); return ( <div className="navbar-header"> - <a className={homeLinkClassName} href={homeUrl}>{this.renderLogo()}</a> + <Link to={homeController} className={homeLinkClassName}>{this.renderLogo()}</Link> </div> ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js index 424ee074cef..d5c2f6402a8 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; +import { Link } from 'react-router'; import { translate } from '../../../../helpers/l10n'; import { isUserAdmin } from '../../../../helpers/users'; @@ -36,48 +37,54 @@ export default class GlobalNavMenu extends React.Component { } renderProjects () { - const controller = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/projects'; - const url = window.baseUrl + controller; + const pathname = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/projects'; return ( - <li className={this.activeLink('/projects')}> - <a href={url}>{translate('projects.page')}</a> + <li> + <Link to={{ pathname }} activeClassName="active"> + {translate('projects.page')} + </Link> </li> ); } renderIssuesLink () { const query = this.props.currentUser.isLoggedIn ? '#resolved=false|assigned_to_me=true' : '#resolved=false'; - const url = window.baseUrl + '/issues' + query; + const url = '/issues' + query; return ( - <li className={this.activeLink('/issues')}> - <a href={url}>{translate('issues.page')}</a> + <li> + <Link to={url} className={this.activeLink('/issues')}> + {translate('issues.page')} + </Link> </li> ); } renderRulesLink () { - const url = window.baseUrl + '/coding_rules'; return ( - <li className={this.activeLink('/coding_rules')}> - <a href={url}>{translate('coding_rules.page')}</a> + <li> + <Link to="/coding_rules" className={this.activeLink('/coding_rules')}> + {translate('coding_rules.page')} + </Link> </li> ); } renderProfilesLink () { - const url = window.baseUrl + '/profiles'; return ( - <li className={this.activeLink('/profiles')}> - <a href={url}>{translate('quality_profiles.page')}</a> + <li> + <Link to="/profiles" activeClassName="active"> + {translate('quality_profiles.page')} + </Link> </li> ); } renderQualityGatesLink () { - const url = window.baseUrl + '/quality_gates'; return ( - <li className={this.activeLink('/quality_gates')}> - <a href={url}>{translate('quality_gates.page')}</a> + <li> + <Link to="/quality_gates" activeClassName="active"> + {translate('quality_gates.page')} + </Link> </li> ); } @@ -86,10 +93,11 @@ export default class GlobalNavMenu extends React.Component { if (!isUserAdmin(this.props.currentUser)) { return null; } - const url = window.baseUrl + '/settings'; return ( - <li className={this.activeLink('/settings')}> - <a className="navbar-admin-link" href={url}>{translate('layout.settings')}</a> + <li> + <Link to="/settings" className="navbar-admin-link" activeClassName="active"> + {translate('layout.settings')} + </Link> </li> ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js index 11f633320c2..47eeb101a9b 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; +import { Link } from 'react-router'; import Avatar from '../../../../components/ui/Avatar'; import RecentHistory from '../component/RecentHistory'; import { translate } from '../../../../helpers/l10n'; @@ -45,7 +46,7 @@ export default class GlobalNavUser extends React.Component { </a> <ul className="dropdown-menu dropdown-menu-right"> <li> - <a href={`${window.baseUrl}/account/`}>{translate('my_account.page')}</a> + <Link to="/account">{translate('my_account.page')}</Link> </li> <li> <a onClick={this.handleLogout} href="#">{translate('layout.logout')}</a> diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js index afe8b25f1f2..6560e94f27c 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js +++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js @@ -19,6 +19,7 @@ */ import React from 'react'; import classNames from 'classnames'; +import { IndexLink } from 'react-router'; import { translate } from '../../../../helpers/l10n'; export default class SettingsNav extends React.Component { @@ -46,7 +47,7 @@ export default class SettingsNav extends React.Component { return this.isSomethingActive(urls); } - renderLink(url, title, highlightUrl = url) { + renderLink (url, title, highlightUrl = url) { const fullUrl = window.baseUrl + url; const isActive = typeof highlightUrl === 'string' ? window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 : @@ -76,7 +77,11 @@ export default class SettingsNav extends React.Component { <div className="navbar-context-inner"> <div className="container"> <ul className="nav navbar-nav nav-crumbs"> - {this.renderLink('/settings', translate('layout.settings'))} + <li> + <IndexLink to="/settings"> + {translate('layout.settings')} + </IndexLink> + </li> </ul> <ul className="nav navbar-nav nav-tabs"> @@ -85,11 +90,31 @@ export default class SettingsNav extends React.Component { {translate('sidebar.project_settings')} <i className="icon-dropdown"/> </a> <ul className="dropdown-menu"> - {this.renderLink('/settings', translate('settings.page'), url => window.location.pathname === url)} - {this.renderLink('/settings/licenses', translate('property.category.licenses'))} - {this.renderLink('/settings/encryption', translate('property.category.security.encryption'))} - {this.renderLink('/settings/server_id', translate('property.category.server_id'))} - {this.renderLink('/metrics', 'Custom Metrics')} + <li> + <IndexLink to="/settings" activeClassName="active"> + {translate('settings.page')} + </IndexLink> + </li> + <li> + <IndexLink to="/settings/licenses" activeClassName="active"> + {translate('property.category.licenses')} + </IndexLink> + </li> + <li> + <IndexLink to="/settings/encryption" activeClassName="active"> + {translate('property.category.security.encryption')} + </IndexLink> + </li> + <li> + <IndexLink to="/settings/server_id" activeClassName="active"> + {translate('property.category.server_id')} + </IndexLink> + </li> + <li> + <IndexLink to="/metrics" activeClassName="active"> + Custom Metrics + </IndexLink> + </li> {this.props.extensions.map(e => this.renderLink(e.url, e.name))} </ul> </li> @@ -99,10 +124,26 @@ export default class SettingsNav extends React.Component { {translate('sidebar.security')} <i className="icon-dropdown"/> </a> <ul className="dropdown-menu"> - {this.renderLink('/users', translate('users.page'))} - {this.renderLink('/groups', translate('user_groups.page'))} - {this.renderLink('/roles/global', translate('global_permissions.page'))} - {this.renderLink('/permission_templates', translate('permission_templates'))} + <li> + <IndexLink to="/users" activeClassName="active"> + {translate('users.page')} + </IndexLink> + </li> + <li> + <IndexLink to="/groups" activeClassName="active"> + {translate('user_groups.page')} + </IndexLink> + </li> + <li> + <IndexLink to="/roles/global" activeClassName="active"> + {translate('global_permissions.page')} + </IndexLink> + </li> + <li> + <IndexLink to="/permission_templates" activeClassName="active"> + {translate('permission_templates')} + </IndexLink> + </li> </ul> </li> @@ -111,8 +152,16 @@ export default class SettingsNav extends React.Component { {translate('sidebar.projects')} <i className="icon-dropdown"/> </a> <ul className="dropdown-menu"> - {this.renderLink('/projects_admin', 'Management')} - {this.renderLink('/background_tasks', translate('background_tasks.page'))} + <li> + <IndexLink to="/projects_admin" activeClassName="active"> + Management + </IndexLink> + </li> + <li> + <IndexLink to="/background_tasks" activeClassName="active"> + {translate('background_tasks.page')} + </IndexLink> + </li> </ul> </li> @@ -121,8 +170,16 @@ export default class SettingsNav extends React.Component { {translate('sidebar.system')} <i className="icon-dropdown"/> </a> <ul className="dropdown-menu"> - {this.renderLink('/updatecenter', translate('update_center.page'))} - {this.renderLink('/system', translate('system_info.page'))} + <li> + <IndexLink to="/updatecenter" activeClassName="active"> + {translate('update_center.page')} + </IndexLink> + </li> + <li> + <IndexLink to="/system" activeClassName="active"> + {translate('system_info.page')} + </IndexLink> + </li> </ul> </li> </ul> |