aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css13
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx11
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js29
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap36
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx (renamed from server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js)99
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.tsx (renamed from server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.js)19
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx (renamed from server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js)24
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap (renamed from server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap)36
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/app/theme.js20
-rw-r--r--server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx (renamed from server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js)63
-rw-r--r--server/sonar-web/src/main/js/apps/account/organizations/actions.ts (renamed from server/sonar-web/src/main/js/apps/account/organizations/actions.js)7
-rw-r--r--server/sonar-web/src/main/js/apps/explore/Explore.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js19
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap108
-rw-r--r--server/sonar-web/src/main/js/components/common/OrganizationAvatar.css48
-rw-r--r--server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx47
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/OrganizationIcon.tsx40
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/icons.ts1
-rw-r--r--server/sonar-web/src/main/js/components/nav/ContextNavBar.css29
-rw-r--r--server/sonar-web/src/main/js/components/nav/NavBarTabs.css6
-rw-r--r--server/sonar-web/src/main/js/components/ui/Avatar.tsx90
-rw-r--r--server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx81
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap23
26 files changed, 531 insertions, 342 deletions
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
index 72b8cbb1c27..ccfd5d9c63e 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
@@ -1,6 +1,8 @@
.navbar-context-favorite {
- float: left;
- padding: 7px 10px 0 0;
+ display: inline-block;
+ vertical-align: top;
+ padding-top: var(--gridSize);
+ padding-left: calc(1.5 * var(--gridSize));
}
.navbar-context-title-qualifier {
@@ -11,9 +13,10 @@
}
.navbar-context-branches {
- float: left;
- padding: 8px 0 6px;
- margin-left: 16px;
+ display: inline-block;
+ vertical-align: top;
+ padding: var(--gridSize) 0;
+ margin-left: calc(2 * var(--gridSize));
line-height: 16px;
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
index 110c8b2a2ef..8f13a285693 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
@@ -25,6 +25,7 @@ import ComponentNavMeta from './ComponentNavMeta';
import ComponentNavMenu from './ComponentNavMenu';
import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif';
import RecentHistory from '../../RecentHistory';
+import * as theme from '../../../theme';
import { Branch, Component } from '../../../types';
import ContextNavBar from '../../../../components/nav/ContextNavBar';
import { getTasksForComponent, PendingTask, Task } from '../../../../api/ce';
@@ -109,16 +110,16 @@ export default class ComponentNav extends React.PureComponent<Props, State> {
return (
<ContextNavBar
id="context-navigation"
- height={notifComponent ? 95 : 65}
+ height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw}
notif={notifComponent}>
- <ComponentNavFavorite
- component={this.props.component.key}
- favorite={this.props.component.isFavorite}
- />
<ComponentNavBreadcrumbs
component={this.props.component}
breadcrumbs={this.props.component.breadcrumbs}
/>
+ <ComponentNavFavorite
+ component={this.props.component.key}
+ favorite={this.props.component.isFavorite}
+ />
{this.props.currentBranch && (
<ComponentNavBranch
branches={this.props.branches}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js
index 8129eeb98fd..23138af7f82 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js
@@ -23,10 +23,12 @@ import { connect } from 'react-redux';
import { Link } from 'react-router';
import QualifierIcon from '../../../../components/shared/QualifierIcon';
import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer';
+import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
import OrganizationHelmet from '../../../../components/common/OrganizationHelmet';
import OrganizationLink from '../../../../components/ui/OrganizationLink';
import PrivateBadge from '../../../../components/common/PrivateBadge';
import { collapsePath, limitComponentName } from '../../../../helpers/path';
+import { getProjectUrl } from '../../../../helpers/urls';
class ComponentNavBreadcrumbs extends React.PureComponent {
static propTypes = {
@@ -52,21 +54,16 @@ class ComponentNavBreadcrumbs extends React.PureComponent {
const itemName = isPath ? collapsePath(item.name, 15) : limitComponentName(item.name);
return (
<span key={item.key}>
- {!displayOrganization &&
- index === 0 && (
- <span className="navbar-context-title-qualifier little-spacer-right">
- <QualifierIcon qualifier={lastItem.qualifier} />
- </span>
- )}
+ {index === 0 && (
+ <span className="navbar-context-title-qualifier spacer-right">
+ <QualifierIcon qualifier={lastItem.qualifier} />
+ </span>
+ )}
<Link
+ className="link-base-color link-no-underline"
title={item.name}
- to={{ pathname: '/dashboard', query: { id: item.key } }}
- className="link-base-color link-no-underline">
- {index === breadcrumbs.length - 1 ? (
- <strong>{itemName}</strong>
- ) : (
- <span>{itemName}</span>
- )}
+ to={getProjectUrl(item.key)}>
+ {itemName}
</Link>
{index < breadcrumbs.length - 1 && <span className="slash-separator" />}
</span>
@@ -81,12 +78,10 @@ class ComponentNavBreadcrumbs extends React.PureComponent {
/>
{displayOrganization && (
<span>
- <span className="navbar-context-title-qualifier little-spacer-right">
- <QualifierIcon qualifier={lastItem.qualifier} />
- </span>
+ <OrganizationAvatar organization={organization} />
<OrganizationLink
organization={organization}
- className="link-base-color link-no-underline">
+ className="link-base-color link-no-underline spacer-left">
{organization.name}
</OrganizationLink>
<span className="slash-separator" />
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
index f92f0e066ea..1baaad7dc1f 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`renders 1`] = `
<ContextNavBar
- height={95}
+ height={92}
id="context-navigation"
notif={
<ComponentNavBgTaskNotif
@@ -27,9 +27,6 @@ exports[`renders 1`] = `
/>
}
>
- <ComponentNavFavorite
- component="component"
- />
<ComponentNavBreadcrumbs
breadcrumbs={
Array [
@@ -56,6 +53,9 @@ exports[`renders 1`] = `
}
}
/>
+ <ComponentNavFavorite
+ component="component"
+ />
<ComponentNavMeta
component={
Object {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap
index 259c976fd11..99cf4b7c00f 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap
@@ -12,7 +12,7 @@ exports[`should not render breadcrumbs with one element 1`] = `
key="my-project"
>
<span
- className="navbar-context-title-qualifier little-spacer-right"
+ className="navbar-context-title-qualifier spacer-right"
>
<QualifierIcon
qualifier="TRK"
@@ -27,14 +27,13 @@ exports[`should not render breadcrumbs with one element 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "my-project",
},
}
}
>
- <strong>
- My Project
- </strong>
+ My Project
</Link>
</span>
</h1>
@@ -54,15 +53,16 @@ exports[`should render organization 1`] = `
title="My Project"
/>
<span>
- <span
- className="navbar-context-title-qualifier little-spacer-right"
- >
- <QualifierIcon
- qualifier="TRK"
- />
- </span>
+ <OrganizationAvatar
+ organization={
+ Object {
+ "key": "foo",
+ "name": "The Foo Organization",
+ }
+ }
+ />
<OrganizationLink
- className="link-base-color link-no-underline"
+ className="link-base-color link-no-underline spacer-left"
organization={
Object {
"key": "foo",
@@ -79,6 +79,13 @@ exports[`should render organization 1`] = `
<span
key="my-project"
>
+ <span
+ className="navbar-context-title-qualifier spacer-right"
+ >
+ <QualifierIcon
+ qualifier="TRK"
+ />
+ </span>
<Link
className="link-base-color link-no-underline"
onlyActiveOnIndex={false}
@@ -88,14 +95,13 @@ exports[`should render organization 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
+ "branch": undefined,
"id": "my-project",
},
}
}
>
- <strong>
- My Project
- </strong>
+ My Project
</Link>
</span>
</h1>
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.tsx
index 3b577558541..3a5a143f963 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.tsx
@@ -17,80 +17,72 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
-import React from 'react';
-import classNames from 'classnames';
+import * as React from 'react';
+import * as classNames from 'classnames';
import { sortBy } from 'lodash';
+import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import * as theme from '../../../theme';
+import { CurrentUser, LoggedInUser, isLoggedIn, Organization } from '../../../types';
import Avatar from '../../../../components/ui/Avatar';
-import OrganizationIcon from '../../../../components/icons-components/OrganizationIcon';
import OrganizationLink from '../../../../components/ui/OrganizationLink';
import { translate } from '../../../../helpers/l10n';
+import { getBaseUrl } from '../../../../helpers/urls';
+import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
-/*::
-type CurrentUser = {
- avatar?: string,
- email?: string,
- isLoggedIn: boolean,
- name: string
-};
-*/
-
-/*::
-type Props = {
- appState: {
- organizationsEnabled: boolean
- },
- currentUser: CurrentUser,
- fetchMyOrganizations: () => Promise<*>,
- location: Object,
- organizations: Array<{ isAdmin: bool, key: string, name: string }>,
- router: { push: string => void }
-};
-*/
-
-/*::
-type State = {
- open: boolean
-};
-*/
-
-export default class GlobalNavUser extends React.PureComponent {
- /*:: node: HTMLElement; */
- /*:: props: Props; */
- state /*: State */ = { open: false };
+interface Props {
+ appState: { organizationsEnabled: boolean };
+ currentUser: CurrentUser;
+ fetchMyOrganizations: () => Promise<void>;
+ organizations: Organization[];
+}
+
+interface State {
+ open: boolean;
+}
+
+export default class GlobalNavUser extends React.PureComponent<Props, State> {
+ node?: HTMLElement | null;
+
+ static contextTypes = {
+ router: PropTypes.object
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = { open: false };
+ }
componentWillUnmount() {
window.removeEventListener('click', this.handleClickOutside);
}
- handleClickOutside = (event /*: { target: HTMLElement } */) => {
- if (!this.node || !this.node.contains(event.target)) {
+ handleClickOutside = (event: MouseEvent) => {
+ if (!this.node || !this.node.contains(event.target as Node)) {
this.closeDropdown();
}
};
- handleLogin = (e /*: Event */) => {
- e.preventDefault();
- const shouldReturnToCurrentPage = window.location.pathname !== `${window.baseUrl}/about`;
+ handleLogin = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ const shouldReturnToCurrentPage = window.location.pathname !== `${getBaseUrl()}/about`;
if (shouldReturnToCurrentPage) {
const returnTo = encodeURIComponent(window.location.pathname + window.location.search);
- window.location =
- window.baseUrl + `/sessions/new?return_to=${returnTo}${window.location.hash}`;
+ window.location.href =
+ getBaseUrl() + `/sessions/new?return_to=${returnTo}${window.location.hash}`;
} else {
- window.location = `${window.baseUrl}/sessions/new`;
+ window.location.href = `${getBaseUrl()}/sessions/new`;
}
};
- handleLogout = (e /*: Event */) => {
- e.preventDefault();
+ handleLogout = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
this.closeDropdown();
- this.props.router.push('/sessions/logout');
+ this.context.router.push('/sessions/logout');
};
- toggleDropdown = (evt /*: Event */) => {
- evt.preventDefault();
+ toggleDropdown = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
if (this.state.open) {
this.closeDropdown();
} else {
@@ -118,7 +110,8 @@ export default class GlobalNavUser extends React.PureComponent {
};
renderAuthenticated() {
- const { currentUser, organizations } = this.props;
+ const { organizations } = this.props;
+ const currentUser = this.props.currentUser as LoggedInUser;
const hasOrganizations = this.props.appState.organizationsEnabled && organizations.length > 0;
return (
<li
@@ -167,10 +160,10 @@ export default class GlobalNavUser extends React.PureComponent {
organization={organization}
onClick={this.closeDropdown}>
<div>
- <OrganizationIcon />
+ <OrganizationAvatar organization={organization} small={true} />
<span className="spacer-left">{organization.name}</span>
</div>
- {organization.isAdmin && (
+ {organization.canAdmin && (
<span className="outline-badge spacer-left">{translate('admin')}</span>
)}
</OrganizationLink>
@@ -199,6 +192,6 @@ export default class GlobalNavUser extends React.PureComponent {
}
render() {
- return this.props.currentUser.isLoggedIn ? this.renderAuthenticated() : this.renderAnonymous();
+ return isLoggedIn(this.props.currentUser) ? this.renderAuthenticated() : this.renderAnonymous();
}
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.tsx
index c84bdff2a7a..f73200791a0 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUserContainer.tsx
@@ -17,19 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
-import { withRouter } from 'react-router';
import { connect } from 'react-redux';
import GlobalNavUser from './GlobalNavUser';
+import { Organization } from '../../../types';
import { fetchMyOrganizations } from '../../../../apps/account/organizations/actions';
import { getMyOrganizations } from '../../../../store/rootReducer';
-const mapStateToProps = state => ({
+interface StateProps {
+ organizations: Organization[];
+}
+
+const mapStateToProps = (state: any): StateProps => ({
organizations: getMyOrganizations(state)
});
+interface DispatchProps {
+ fetchMyOrganizations: () => Promise<void>;
+}
+
const mapDispatchToProps = {
- fetchMyOrganizations
-};
+ fetchMyOrganizations: fetchMyOrganizations as any
+} as DispatchProps;
-export default connect(mapStateToProps, mapDispatchToProps)(withRouter(GlobalNavUser));
+export default connect(mapStateToProps, mapDispatchToProps)(GlobalNavUser);
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx
index 4fb4f28f2d2..1ffec34be8f 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx
@@ -17,15 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
+import * as React from 'react';
import { shallow } from 'enzyme';
import GlobalNavUser from '../GlobalNavUser';
const currentUser = { avatar: 'abcd1234', isLoggedIn: true, name: 'foo', email: 'foo@bar.baz' };
const organizations = [
- { key: 'myorg', name: 'MyOrg' },
- { key: 'foo', name: 'Foo' },
- { key: 'bar', name: 'bar' }
+ { key: 'myorg', name: 'MyOrg', projectVisibility: 'public' },
+ { key: 'foo', name: 'Foo', projectVisibility: 'public' },
+ { key: 'bar', name: 'bar', projectVisibility: 'public' }
];
const appState = { organizationsEnabled: true };
@@ -35,7 +35,7 @@ it('should render the right interface for anonymous user', () => {
<GlobalNavUser
appState={appState}
currentUser={currentUser}
- fetchMyOrganizations={() => {}}
+ fetchMyOrganizations={jest.fn()}
organizations={[]}
/>
);
@@ -47,7 +47,7 @@ it('should render the right interface for logged in user', () => {
<GlobalNavUser
appState={appState}
currentUser={currentUser}
- fetchMyOrganizations={() => {}}
+ fetchMyOrganizations={jest.fn()}
organizations={[]}
/>
);
@@ -60,7 +60,7 @@ it('should render the users organizations', () => {
<GlobalNavUser
appState={appState}
currentUser={currentUser}
- fetchMyOrganizations={() => {}}
+ fetchMyOrganizations={jest.fn()}
organizations={organizations}
/>
);
@@ -73,7 +73,7 @@ it('should not render the users organizations when they are not activated', () =
<GlobalNavUser
appState={{ organizationsEnabled: false }}
currentUser={currentUser}
- fetchMyOrganizations={() => {}}
+ fetchMyOrganizations={jest.fn()}
organizations={organizations}
/>
);
@@ -109,9 +109,9 @@ it('should lazyload the organizations when opening the dropdown', () => {
/>
);
expect(fetchMyOrganizations.mock.calls.length).toBe(0);
- wrapper.instance().openDropdown();
+ (wrapper.instance() as GlobalNavUser).openDropdown();
expect(fetchMyOrganizations.mock.calls.length).toBe(1);
- wrapper.instance().openDropdown();
+ (wrapper.instance() as GlobalNavUser).openDropdown();
expect(fetchMyOrganizations.mock.calls.length).toBe(2);
});
@@ -125,11 +125,11 @@ it('should update the organizations when the user changes', () => {
organizations={organizations}
/>
);
- wrapper.instance().openDropdown();
+ (wrapper.instance() as GlobalNavUser).openDropdown();
expect(fetchMyOrganizations.mock.calls.length).toBe(1);
wrapper.setProps({
currentUser: { isLoggedIn: true, name: 'test', email: 'test@sonarsource.com' }
});
- wrapper.instance().openDropdown();
+ (wrapper.instance() as GlobalNavUser).openDropdown();
expect(fetchMyOrganizations.mock.calls.length).toBe(2);
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
index 85ca26d59b2..3bb56aac675 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
@@ -207,11 +207,21 @@ exports[`should render the users organizations 1`] = `
Object {
"key": "bar",
"name": "bar",
+ "projectVisibility": "public",
}
}
>
<div>
- <OrganizationIcon />
+ <OrganizationAvatar
+ organization={
+ Object {
+ "key": "bar",
+ "name": "bar",
+ "projectVisibility": "public",
+ }
+ }
+ small={true}
+ />
<span
className="spacer-left"
>
@@ -230,11 +240,21 @@ exports[`should render the users organizations 1`] = `
Object {
"key": "foo",
"name": "Foo",
+ "projectVisibility": "public",
}
}
>
<div>
- <OrganizationIcon />
+ <OrganizationAvatar
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ small={true}
+ />
<span
className="spacer-left"
>
@@ -253,11 +273,21 @@ exports[`should render the users organizations 1`] = `
Object {
"key": "myorg",
"name": "MyOrg",
+ "projectVisibility": "public",
}
}
>
<div>
- <OrganizationIcon />
+ <OrganizationAvatar
+ organization={
+ Object {
+ "key": "myorg",
+ "name": "MyOrg",
+ "projectVisibility": "public",
+ }
+ }
+ small={true}
+ />
<span
className="spacer-left"
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
index db5f9efcf95..54d91d77c75 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import * as classNames from 'classnames';
import { IndexLink, Link } from 'react-router';
+import * as theme from '../../../../app/theme';
import ContextNavBar from '../../../../components/nav/ContextNavBar';
import SettingsEditionsNotifContainer from './SettingsEditionsNotifContainer';
import NavBarTabs from '../../../../components/nav/NavBarTabs';
@@ -203,11 +204,9 @@ export default class SettingsNav extends React.PureComponent<Props> {
return (
<ContextNavBar
id="context-navigation"
- height={notifComponent ? 95 : 65}
+ height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw}
notif={notifComponent}>
- <h1 className="navbar-context-header">
- <strong>{translate('layout.settings')}</strong>
- </h1>
+ <h1 className="navbar-context-header">{translate('layout.settings')}</h1>
<NavBarTabs>
{this.renderConfigurationTab()}
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
index e33f8690286..3208552f512 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
@@ -2,15 +2,13 @@
exports[`should work with extensions 1`] = `
<ContextNavBar
- height={65}
+ height={72}
id="context-navigation"
>
<h1
className="navbar-context-header"
>
- <strong>
- layout.settings
- </strong>
+ layout.settings
</h1>
<NavBarTabs>
<li
diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js
index 6e3f0fc5c51..743dae7e7a9 100644
--- a/server/sonar-web/src/main/js/app/theme.js
+++ b/server/sonar-web/src/main/js/app/theme.js
@@ -20,6 +20,8 @@
// IMPORTANT: any change in this file requires restart of the dev server
+const grid = 8;
+
module.exports = {
// colors
blue: '#4b9fd5',
@@ -48,19 +50,23 @@ module.exports = {
leakBorderColor: '#eae3c7',
// sizes
+ gridSize: `${grid}px`,
+
baseFontSize: '13px',
smallFontSize: '12px',
mediumFontSize: '14px',
bigFontSize: '16px',
- controlHeight: '24px',
- smallControlHeight: '20px',
- tinyControlHeight: '16px',
+ controlHeight: `${3 * grid}px`,
+ smallControlHeight: `${2.5 * grid}px`,
+ tinyControlHeight: `${2 * grid}px`,
+
+ globalNavHeight: `${6 * grid}px`,
+ globalNavHeightRaw: 6 * grid,
+ globalNavContentHeight: `${4 * grid}px`,
+ globalNavContentHeightRaw: 4 * grid,
- globalNavHeight: '48px',
- globalNavHeightRaw: 48,
- globalNavContentHeight: '32px',
- globalNavContentHeightRaw: 32,
+ contextNavHeightRaw: 9 * grid,
// different
defaultShadow: '0 6px 12px rgba(0, 0, 0, 0.175)',
diff --git a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx
index 95794c931c5..75a48b2bed9 100644
--- a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js
+++ b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx
@@ -17,8 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
-import React from 'react';
+import * as React from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { Link } from 'react-router';
@@ -26,41 +25,49 @@ import OrganizationsList from './OrganizationsList';
import { translate } from '../../../helpers/l10n';
import { fetchIfAnyoneCanCreateOrganizations, fetchMyOrganizations } from './actions';
import { getAppState, getMyOrganizations, getGlobalSettingValue } from '../../../store/rootReducer';
-/*:: import type { Organization } from '../../../store/organizations/duck'; */
-
-class UserOrganizations extends React.PureComponent {
- /*:: mounted: boolean; */
-
- /*:: props: {
- anyoneCanCreate?: { value: string },
- canAdmin: boolean,
- children?: React.Element<*>,
- organizations: Array<Organization>,
- fetchIfAnyoneCanCreateOrganizations: () => Promise<*>,
- fetchMyOrganizations: () => Promise<*>
- };
-*/
+import { Organization } from '../../../app/types';
- state /*: { loading: boolean } */ = {
- loading: true
- };
+interface StateProps {
+ anyoneCanCreate?: { value: string };
+ canAdmin: boolean;
+ organizations: Array<Organization>;
+}
+
+interface DispatchProps {
+ fetchIfAnyoneCanCreateOrganizations: () => Promise<void>;
+ fetchMyOrganizations: () => Promise<void>;
+}
+
+interface Props extends StateProps, DispatchProps {
+ children?: React.ReactNode;
+}
+
+interface State {
+ loading: boolean;
+}
+
+class UserOrganizations extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
componentDidMount() {
this.mounted = true;
Promise.all([
this.props.fetchMyOrganizations(),
this.props.fetchIfAnyoneCanCreateOrganizations()
- ]).then(() => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- });
+ ]).then(this.stopLoading, this.stopLoading);
}
componentWillUnmount() {
this.mounted = false;
}
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
render() {
const anyoneCanCreate =
this.props.anyoneCanCreate != null && this.props.anyoneCanCreate.value === 'true';
@@ -103,15 +110,15 @@ class UserOrganizations extends React.PureComponent {
}
}
-const mapStateToProps = state => ({
+const mapStateToProps = (state: any): StateProps => ({
anyoneCanCreate: getGlobalSettingValue(state, 'sonar.organizations.anyoneCanCreate'),
canAdmin: getAppState(state).canAdmin,
organizations: getMyOrganizations(state)
});
const mapDispatchToProps = {
- fetchMyOrganizations,
- fetchIfAnyoneCanCreateOrganizations
-};
+ fetchMyOrganizations: fetchMyOrganizations as any,
+ fetchIfAnyoneCanCreateOrganizations: fetchIfAnyoneCanCreateOrganizations as any
+} as DispatchProps;
export default connect(mapStateToProps, mapDispatchToProps)(UserOrganizations);
diff --git a/server/sonar-web/src/main/js/apps/account/organizations/actions.js b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts
index 38f30b159b0..f93bcdd1633 100644
--- a/server/sonar-web/src/main/js/apps/account/organizations/actions.js
+++ b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts
@@ -17,19 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Dispatch } from 'redux';
import { getOrganizations } from '../../../api/organizations';
import { receiveMyOrganizations } from '../../../store/organizations/duck';
import { getValues } from '../../../api/settings';
import { receiveValues } from '../../settings/store/values/actions';
-export const fetchMyOrganizations = () => dispatch => {
+export const fetchMyOrganizations = () => (dispatch: Dispatch<any>) => {
return getOrganizations({ member: true }).then(({ organizations }) => {
return dispatch(receiveMyOrganizations(organizations));
});
};
-export const fetchIfAnyoneCanCreateOrganizations = () => dispatch => {
+export const fetchIfAnyoneCanCreateOrganizations = () => (dispatch: Dispatch<any>) => {
return getValues('sonar.organizations.anyoneCanCreate').then(values => {
- dispatch(receiveValues(values));
+ dispatch(receiveValues(values, undefined));
});
};
diff --git a/server/sonar-web/src/main/js/apps/explore/Explore.tsx b/server/sonar-web/src/main/js/apps/explore/Explore.tsx
index 7cb0c776b2b..a1ed154d36a 100644
--- a/server/sonar-web/src/main/js/apps/explore/Explore.tsx
+++ b/server/sonar-web/src/main/js/apps/explore/Explore.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
+import * as theme from '../../app/theme';
import ContextNavBar from '../../components/nav/ContextNavBar';
import NavBarTabs from '../../components/nav/NavBarTabs';
import { translate } from '../../helpers/l10n';
@@ -30,7 +31,7 @@ interface Props {
export default function Explore(props: Props) {
return (
<div id="explore">
- <ContextNavBar id="explore-navigation" height={65}>
+ <ContextNavBar id="explore-navigation" height={theme.contextNavHeightRaw}>
<div className="navbar-context-header">
<h1 className="display-inline-block">{translate('explore')}</h1>
</div>
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
index 43728684b84..507d43bbfb0 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
@@ -21,10 +21,11 @@
import React from 'react';
import { Link } from 'react-router';
import classNames from 'classnames';
+import * as theme from '../../../app/theme';
import { translate } from '../../../helpers/l10n';
import ContextNavBar from '../../../components/nav/ContextNavBar';
import NavBarTabs from '../../../components/nav/NavBarTabs';
-import OrganizationIcon from '../../../components/icons-components/OrganizationIcon';
+import OrganizationAvatar from '../../../components/common/OrganizationAvatar';
import { getQualityGatesUrl } from '../../../helpers/urls';
/*:: import type { Organization } from '../../../store/organizations/duck'; */
@@ -153,14 +154,14 @@ export default class OrganizationNavigation extends React.PureComponent {
const moreActive = !adminActive && location.pathname.includes('/extension/');
return (
- <ContextNavBar id="context-navigation" height={65}>
+ <ContextNavBar id="context-navigation" height={theme.contextNavHeightRaw}>
<div className="navbar-context-header">
<h1 className="display-inline-block">
- <OrganizationIcon className="little-spacer-right" />
+ <OrganizationAvatar organization={organization} />
<Link
to={`/organizations/${organization.key}`}
- className="link-base-color link-no-underline">
- <strong>{organization.name}</strong>
+ className="link-base-color link-no-underline spacer-left">
+ {organization.name}
</Link>
</h1>
{organization.description != null && (
@@ -173,9 +174,9 @@ export default class OrganizationNavigation extends React.PureComponent {
</div>
<div className="navbar-context-meta">
- {!!organization.avatar && (
- <img src={organization.avatar} height={30} alt={organization.name} />
- )}
+ <div className="text-muted">
+ <strong>{translate('organization.key')}:</strong> {organization.key}
+ </div>
{organization.url != null && (
<div>
<p className="text-limited text-top">
@@ -191,7 +192,7 @@ export default class OrganizationNavigation extends React.PureComponent {
)}
</div>
- <NavBarTabs>
+ <NavBarTabs className="navbar-context-tabs">
<li>
<Link
to={`/organizations/${organization.key}/projects`}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
index c4dcad9c707..66184bbc5d4 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
@@ -2,7 +2,7 @@
exports[`admin 1`] = `
<ContextNavBar
- height={65}
+ height={72}
id="context-navigation"
>
<div
@@ -11,25 +11,43 @@ exports[`admin 1`] = `
<h1
className="display-inline-block"
>
- <OrganizationIcon
- className="little-spacer-right"
+ <OrganizationAvatar
+ organization={
+ Object {
+ "canAdmin": true,
+ "canDelete": true,
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
/>
<Link
- className="link-base-color link-no-underline"
+ className="link-base-color link-no-underline spacer-left"
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/foo"
>
- <strong>
- Foo
- </strong>
+ Foo
</Link>
</h1>
</div>
<div
className="navbar-context-meta"
- />
- <NavBarTabs>
+ >
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+ </div>
+ <NavBarTabs
+ className="navbar-context-tabs"
+ >
<li>
<Link
className=""
@@ -187,7 +205,7 @@ exports[`admin 1`] = `
exports[`regular user 1`] = `
<ContextNavBar
- height={65}
+ height={72}
id="context-navigation"
>
<div
@@ -196,25 +214,43 @@ exports[`regular user 1`] = `
<h1
className="display-inline-block"
>
- <OrganizationIcon
- className="little-spacer-right"
+ <OrganizationAvatar
+ organization={
+ Object {
+ "canAdmin": false,
+ "canDelete": false,
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
/>
<Link
- className="link-base-color link-no-underline"
+ className="link-base-color link-no-underline spacer-left"
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/foo"
>
- <strong>
- Foo
- </strong>
+ Foo
</Link>
</h1>
</div>
<div
className="navbar-context-meta"
- />
- <NavBarTabs>
+ >
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+ </div>
+ <NavBarTabs
+ className="navbar-context-tabs"
+ >
<li>
<Link
className=""
@@ -292,7 +328,7 @@ exports[`regular user 1`] = `
exports[`undeletable org 1`] = `
<ContextNavBar
- height={65}
+ height={72}
id="context-navigation"
>
<div
@@ -301,25 +337,43 @@ exports[`undeletable org 1`] = `
<h1
className="display-inline-block"
>
- <OrganizationIcon
- className="little-spacer-right"
+ <OrganizationAvatar
+ organization={
+ Object {
+ "canAdmin": true,
+ "canDelete": false,
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
/>
<Link
- className="link-base-color link-no-underline"
+ className="link-base-color link-no-underline spacer-left"
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/foo"
>
- <strong>
- Foo
- </strong>
+ Foo
</Link>
</h1>
</div>
<div
className="navbar-context-meta"
- />
- <NavBarTabs>
+ >
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+ </div>
+ <NavBarTabs
+ className="navbar-context-tabs"
+ >
<li>
<Link
className=""
diff --git a/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css
new file mode 100644
index 00000000000..9099fba90c2
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact 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.
+ */
+.navbar-context-avatar {
+ display: inline-flex;
+ vertical-align: top;
+ justify-content: center;
+ align-items: center;
+ width: calc(4 * var(--gridSize));
+ height: calc(4 * var(--gridSize));
+ border: 1px solid var(--barBorderColor);
+}
+
+.navbar-context-avatar.is-empty {
+ border: none;
+}
+
+.navbar-context-avatar.is-small {
+ width: calc(2 * var(--gridSize));
+ height: calc(2 * var(--gridSize));
+}
+
+.navbar-context-avatar img {
+ vertical-align: top;
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.navbar-context-avatar img,
+.navbar-context-avatar svg {
+ transform: none;
+}
diff --git a/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx
new file mode 100644
index 00000000000..6c9ed12eca3
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import * as classNames from 'classnames';
+import GenericAvatar from '../ui/GenericAvatar';
+import './OrganizationAvatar.css';
+
+interface Props {
+ organization: {
+ avatar?: string;
+ name: string;
+ };
+ small?: boolean;
+}
+
+export default function OrganizationAvatar({ organization, small }: Props) {
+ return (
+ <div
+ className={classNames('navbar-context-avatar', 'rounded', {
+ 'is-empty': !organization.avatar,
+ 'is-small': small
+ })}>
+ {organization.avatar ? (
+ <img className="rounded" src={organization.avatar} alt={organization.name} />
+ ) : (
+ <GenericAvatar name={organization.name} size={small ? 15 : 30} />
+ )}
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/OrganizationIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OrganizationIcon.tsx
deleted file mode 100644
index dc89017c23d..00000000000
--- a/server/sonar-web/src/main/js/components/icons-components/OrganizationIcon.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 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 React from 'react';
-import * as theme from '../../app/theme';
-import { IconProps } from './types';
-
-export default function OrganizationIcon({ className, fill = theme.blue, size = 16 }: IconProps) {
- return (
- <svg
- className={className}
- width={size}
- height={size}
- viewBox="0 0 16 16"
- version="1.1"
- xmlnsXlink="http://www.w3.org/1999/xlink"
- xmlSpace="preserve">
- <path
- style={{ fill }}
- d="M13.5 6c-.4 0-.7.1-1.1.2L11 4.8v-.3C11 3.1 9.9 2 8.5 2S6 3.1 6 4.5v.2L4.5 6.2c-.3-.1-.7-.2-1-.2C2.1 6 1 7.1 1 8.5S2.1 11 3.5 11 6 9.9 6 8.5c0-.7-.3-1.3-.7-1.7l1-1c.4.6 1 1 1.7 1.1V9c-1.1.2-2 1.2-2 2.4C6 12.9 7.1 14 8.5 14s2.5-1.1 2.5-2.5c0-1.2-.9-2.2-2-2.4V6.9c.7-.1 1.2-.5 1.6-1.1l1 1c-.4.4-.6 1-.6 1.6 0 1.4 1.1 2.5 2.5 2.5s2.5-1 2.5-2.4S14.9 6 13.5 6zm-10 4C2.7 10 2 9.3 2 8.5S2.7 7 3.5 7 5 7.7 5 8.5 4.3 10 3.5 10zm6.5 1.5c0 .8-.7 1.5-1.5 1.5S7 12.3 7 11.5 7.7 10 8.5 10s1.5.7 1.5 1.5zM8.5 6C7.7 6 7 5.3 7 4.5S7.7 3 8.5 3s1.5.7 1.5 1.5S9.3 6 8.5 6zm5 4c-.8 0-1.5-.7-1.5-1.5S12.7 7 13.5 7s1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"
- />
- </svg>
- );
-}
diff --git a/server/sonar-web/src/main/js/components/icons-components/icons.ts b/server/sonar-web/src/main/js/components/icons-components/icons.ts
index d84d2ac30c4..fad7ac6b86c 100644
--- a/server/sonar-web/src/main/js/components/icons-components/icons.ts
+++ b/server/sonar-web/src/main/js/components/icons-components/icons.ts
@@ -40,7 +40,6 @@ export { default as LinkIcon } from './LinkIcon';
export { default as ListIcon } from './ListIcon';
export { default as LongLivingBranchIcon } from './LongLivingBranchIcon';
export { default as OpenCloseIcon } from './OpenCloseIcon';
-export { default as OrganizationIcon } from './OrganizationIcon';
export { default as PendingIcon } from './PendingIcon';
export { default as ProjectEventIcon } from './ProjectEventIcon';
export { default as PullRequestIcon } from './PullRequestIcon';
diff --git a/server/sonar-web/src/main/js/components/nav/ContextNavBar.css b/server/sonar-web/src/main/js/components/nav/ContextNavBar.css
index 4aa4bd60b16..489747900bf 100644
--- a/server/sonar-web/src/main/js/components/nav/ContextNavBar.css
+++ b/server/sonar-web/src/main/js/components/nav/ContextNavBar.css
@@ -5,7 +5,7 @@
}
.navbar-context .navbar-inner {
- padding-top: 5px;
+ padding-top: var(--gridSize);
border-bottom: 1px solid var(--barBorderColor);
}
@@ -14,16 +14,35 @@
}
.navbar-context-header {
- float: left;
- line-height: 30px;
- font-size: 15px;
+ display: inline-block;
+ height: calc(4 * var(--gridSize));
+ line-height: calc(4 * var(--gridSize));
+ font-size: var(--bigFontSize);
+}
+
+.navbar-context-header h1 {
+ vertical-align: top;
+ line-height: calc(4 * var(--gridSize));
+}
+
+.navbar-context-header .slash-separator {
+ display: inline-block;
+ vertical-align: top;
+ height: calc(4 * var(--gridSize));
+ margin-left: var(--gridSize);
+ margin-right: var(--gridSize);
+ font-size: 24px;
+}
+
+.navbar-context-header .slash-separator::after {
+ color: rgba(68, 68, 68, 0.2);
}
.navbar-context-meta {
position: absolute;
top: 0;
right: 0;
- line-height: 30px;
+ line-height: calc(4 * var(--gridSize));
padding: 0 10px;
color: var(--secondFontColor);
font-size: var(--smallFontSize);
diff --git a/server/sonar-web/src/main/js/components/nav/NavBarTabs.css b/server/sonar-web/src/main/js/components/nav/NavBarTabs.css
index d08552ddf87..288ce5fd315 100644
--- a/server/sonar-web/src/main/js/components/nav/NavBarTabs.css
+++ b/server/sonar-web/src/main/js/components/nav/NavBarTabs.css
@@ -2,6 +2,8 @@
display: flex;
align-items: center;
clear: left;
+ height: var(--controlHeight);
+ margin-top: var(--gridSize);
}
.navbar-tabs > li + li {
@@ -10,8 +12,10 @@
.navbar-tabs > li > a {
display: block;
- padding: 7px 0 4px;
+ height: var(--controlHeight);
+ line-height: calc(var(--controlHeight) - 6px);
border-bottom: 3px solid transparent;
+ box-sizing: border-box;
color: var(--baseFontColor);
transition: none;
}
diff --git a/server/sonar-web/src/main/js/components/ui/Avatar.tsx b/server/sonar-web/src/main/js/components/ui/Avatar.tsx
index d9f09b24076..feb5253d1cb 100644
--- a/server/sonar-web/src/main/js/components/ui/Avatar.tsx
+++ b/server/sonar-web/src/main/js/components/ui/Avatar.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import { connect } from 'react-redux';
import * as classNames from 'classnames';
import { getGlobalSettingValue } from '../../store/rootReducer';
+import GenericAvatar from './GenericAvatar';
interface Props {
className?: string;
@@ -31,58 +32,24 @@ interface Props {
size: number;
}
-class Avatar extends React.PureComponent<Props> {
- renderFallback() {
- const className = classNames(this.props.className, 'rounded');
- const color = stringToColor(this.props.name);
-
- let text = '';
- const words = this.props.name.split(/\s+/).filter(word => word.length > 0);
- if (words.length >= 2) {
- text = words[0][0] + words[1][0];
- } else if (this.props.name.length > 0) {
- text = this.props.name[0];
- }
-
- return (
- <div
- className={className}
- style={{
- backgroundColor: color,
- color: getTextColor(color),
- display: 'inline-block',
- fontSize: Math.min(this.props.size / 2, 14),
- fontWeight: 'normal',
- height: this.props.size,
- lineHeight: `${this.props.size}px`,
- textAlign: 'center',
- verticalAlign: 'top',
- width: this.props.size
- }}>
- {text.toUpperCase()}
- </div>
- );
+function Avatar(props: Props) {
+ if (!props.enableGravatar || !props.hash) {
+ return <GenericAvatar className={props.className} name={props.name} size={props.size} />;
}
- render() {
- if (!this.props.enableGravatar || !this.props.hash) {
- return this.renderFallback();
- }
-
- const url = this.props.gravatarServerUrl
- .replace('{EMAIL_MD5}', this.props.hash)
- .replace('{SIZE}', String(this.props.size * 2));
-
- return (
- <img
- className={classNames(this.props.className, 'rounded')}
- src={url}
- width={this.props.size}
- height={this.props.size}
- alt={this.props.name}
- />
- );
- }
+ const url = props.gravatarServerUrl
+ .replace('{EMAIL_MD5}', props.hash)
+ .replace('{SIZE}', String(props.size * 2));
+
+ return (
+ <img
+ className={classNames(props.className, 'rounded')}
+ src={url}
+ width={props.size}
+ height={props.size}
+ alt={props.name}
+ />
+ );
}
const mapStateToProps = (state: any) => ({
@@ -93,26 +60,3 @@ const mapStateToProps = (state: any) => ({
export default connect(mapStateToProps)(Avatar);
export const unconnectedAvatar = Avatar;
-
-/* eslint-disable no-bitwise, no-mixed-operators */
-function stringToColor(str: string) {
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
- let color = '#';
- for (let i = 0; i < 3; i++) {
- const value = (hash >> (i * 8)) & 0xff;
- color += ('00' + value.toString(16)).substr(-2);
- }
- return color;
-}
-
-function getTextColor(background: string) {
- const rgb = parseInt(background.substr(1), 16);
- const r = (rgb >> 16) & 0xff;
- const g = (rgb >> 8) & 0xff;
- const b = (rgb >> 0) & 0xff;
- const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
- return luma > 140 ? '#222' : '#fff';
-}
diff --git a/server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx b/server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx
new file mode 100644
index 00000000000..36498b69b1b
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 React from 'react';
+import * as classNames from 'classnames';
+
+interface Props {
+ className?: string;
+ name: string;
+ size: number;
+}
+
+export default function GenericAvatar({ className, name, size }: Props) {
+ const color = stringToColor(name);
+
+ let text = '';
+ const words = name.split(/\s+/).filter(word => word.length > 0);
+ if (words.length >= 2) {
+ text = words[0][0] + words[1][0];
+ } else if (name.length > 0) {
+ text = name[0];
+ }
+
+ return (
+ <div
+ className={classNames(className, 'rounded')}
+ style={{
+ backgroundColor: color,
+ color: getTextColor(color),
+ display: 'inline-block',
+ fontSize: Math.min(size / 2, 14),
+ fontWeight: 'normal',
+ height: size,
+ lineHeight: `${size}px`,
+ textAlign: 'center',
+ verticalAlign: 'top',
+ width: size
+ }}>
+ {text.toUpperCase()}
+ </div>
+ );
+}
+
+/* eslint-disable no-bitwise, no-mixed-operators */
+function stringToColor(str: string) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ let color = '#';
+ for (let i = 0; i < 3; i++) {
+ const value = (hash >> (i * 8)) & 0xff;
+ color += ('00' + value.toString(16)).substr(-2);
+ }
+ return color;
+}
+
+function getTextColor(background: string) {
+ const rgb = parseInt(background.substr(1), 16);
+ const r = (rgb >> 16) & 0xff;
+ const g = (rgb >> 8) & 0xff;
+ const b = (rgb >> 0) & 0xff;
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ return luma > 140 ? '#222' : '#fff';
+}
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap
index 6dd7bc59142..0083612ce2f 100644
--- a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap
@@ -1,25 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`falls back to dummy avatar 1`] = `
-<div
- className="rounded"
- style={
- Object {
- "backgroundColor": "#79e189",
- "color": "#222",
- "display": "inline-block",
- "fontSize": 14,
- "fontWeight": "normal",
- "height": 30,
- "lineHeight": "30px",
- "textAlign": "center",
- "verticalAlign": "top",
- "width": 30,
- }
- }
->
- FB
-</div>
+<GenericAvatar
+ name="Foo Bar"
+ size={30}
+/>
`;
exports[`should be able to render with hash only 1`] = `