summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClement Ho <clemmakesapps@gmail.com>2018-08-01 19:43:50 +0000
committerClement Ho <clemmakesapps@gmail.com>2018-08-01 19:43:50 +0000
commit92e079ede29d4d1e1c4a36166aa76ea13e3412d3 (patch)
tree1b8b25c8ca0f7e048aed07de9cdf24a44fc58c36
parentc1fc33d590b3f853ec820fa33ebc114b86af692d (diff)
parentfbfe04401deb7a08da03502282531364aa25d511 (diff)
downloadgitlab-ce-92e079ede29d4d1e1c4a36166aa76ea13e3412d3.tar.gz
Merge branch 'backstage/avatar-helper-vanilla-js' into 'master'
Add vanilla JS avatar_helper and update existing avatar helpers See merge request gitlab-org/gitlab-ce!20886
-rw-r--r--app/assets/javascripts/helpers/avatar_helper.js33
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js14
-rw-r--r--app/assets/javascripts/vue_shared/components/identicon.vue26
-rw-r--r--app/assets/stylesheets/framework/avatar.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss12
-rw-r--r--app/helpers/avatars_helper.rb16
-rw-r--r--spec/javascripts/avatar_helper_spec.js98
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js17
-rw-r--r--spec/javascripts/vue_shared/components/identicon_spec.js17
9 files changed, 200 insertions, 45 deletions
diff --git a/app/assets/javascripts/helpers/avatar_helper.js b/app/assets/javascripts/helpers/avatar_helper.js
new file mode 100644
index 00000000000..d3b1d0f11fd
--- /dev/null
+++ b/app/assets/javascripts/helpers/avatar_helper.js
@@ -0,0 +1,33 @@
+import _ from 'underscore';
+import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
+
+export const DEFAULT_SIZE_CLASS = 's40';
+export const IDENTICON_BG_COUNT = 7;
+
+export function getIdenticonBackgroundClass(entityId) {
+ const type = (entityId % IDENTICON_BG_COUNT) + 1;
+ return `bg${type}`;
+}
+
+export function getIdenticonTitle(entityName) {
+ return getFirstCharacterCapitalized(entityName) || ' ';
+}
+
+export function renderIdenticon(entity, options = {}) {
+ const { sizeClass = DEFAULT_SIZE_CLASS } = options;
+
+ const bgClass = getIdenticonBackgroundClass(entity.id);
+ const title = getIdenticonTitle(entity.name);
+
+ return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape(title)}</div>`;
+}
+
+export function renderAvatar(entity, options = {}) {
+ if (!entity.avatar_url) {
+ return renderIdenticon(entity, options);
+ }
+
+ const { sizeClass = DEFAULT_SIZE_CLASS } = options;
+
+ return `<img src="${_.escape(entity.avatar_url)}" class="avatar ${_.escape(sizeClass)}" />`;
+}
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 5f25c6ce1ae..2be3c97bd95 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -76,6 +76,20 @@ export function capitalizeFirstCharacter(text) {
}
/**
+ * Returns the first character capitalized
+ *
+ * If falsey, returns empty string.
+ *
+ * @param {String} text
+ * @return {String}
+ */
+export function getFirstCharacterCapitalized(text) {
+ return text
+ ? text.charAt(0).toUpperCase()
+ : '';
+}
+
+/**
* Replaces all html tags from a string with the given replacement.
*
* @param {String} string
diff --git a/app/assets/javascripts/vue_shared/components/identicon.vue b/app/assets/javascripts/vue_shared/components/identicon.vue
index 4ffc811e714..0862f2c0cff 100644
--- a/app/assets/javascripts/vue_shared/components/identicon.vue
+++ b/app/assets/javascripts/vue_shared/components/identicon.vue
@@ -1,4 +1,6 @@
<script>
+import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
+
export default {
props: {
entityId: {
@@ -16,26 +18,11 @@ export default {
},
},
computed: {
- /**
- * This method is based on app/helpers/avatars_helper.rb#project_identicon
- */
- identiconStyles() {
- const allowedColors = [
- '#FFEBEE',
- '#F3E5F5',
- '#E8EAF6',
- '#E3F2FD',
- '#E0F2F1',
- '#FBE9E7',
- '#EEEEEE',
- ];
-
- const backgroundColor = allowedColors[this.entityId % 7];
-
- return `background-color: ${backgroundColor}; color: #555;`;
+ identiconBackgroundClass() {
+ return getIdenticonBackgroundClass(this.entityId);
},
identiconTitle() {
- return this.entityName.charAt(0).toUpperCase();
+ return getIdenticonTitle(this.entityName);
},
},
};
@@ -43,8 +30,7 @@ export default {
<template>
<div
- :class="sizeClass"
- :style="identiconStyles"
+ :class="[sizeClass, identiconBackgroundClass]"
class="avatar identicon">
{{ identiconTitle }}
</div>
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 94fa7993133..dddd07c798c 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -69,7 +69,10 @@
.identicon {
text-align: center;
vertical-align: top;
+ color: $identicon-fg-color;
+ background-color: $identicon-gray;
+ // Sizes
&.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 13px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; }
@@ -82,6 +85,15 @@
&.s110 { font-size: 40px; line-height: 108px; font-weight: $gl-font-weight-normal; }
&.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; }
+
+ // Background colors
+ &.bg1 { background-color: $identicon-red; }
+ &.bg2 { background-color: $identicon-purple; }
+ &.bg3 { background-color: $identicon-indigo; }
+ &.bg4 { background-color: $identicon-blue; }
+ &.bg5 { background-color: $identicon-teal; }
+ &.bg6 { background-color: $identicon-orange; }
+ &.bg7 { background-color: $identicon-gray; }
}
.avatar-container {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 08755b4b545..56940a7564a 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -487,6 +487,18 @@ $note-icon-gutter-width: 55px;
$zen-control-color: #555;
/*
+* Identicon
+*/
+$identicon-red: #ffebee;
+$identicon-purple: #f3e5f5;
+$identicon-indigo: #e8eaf6;
+$identicon-blue: #e3f2fd;
+$identicon-teal: #e0f2f1;
+$identicon-orange: #fbe9e7;
+$identicon-gray: $gray-darker;
+$identicon-fg-color: #555555;
+
+/*
* Calendar
*/
$calendar-hover-bg: #ecf3fe;
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 43d92bde064..d48dae8f06d 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -15,22 +15,12 @@ module AvatarsHelper
end
def project_identicon(project, options = {})
- allowed_colors = {
- red: 'FFEBEE',
- purple: 'F3E5F5',
- indigo: 'E8EAF6',
- blue: 'E3F2FD',
- teal: 'E0F2F1',
- orange: 'FBE9E7',
- gray: 'EEEEEE'
- }
-
+ bg_key = (project.id % 7) + 1
options[:class] ||= ''
options[:class] << ' identicon'
- bg_key = project.id % 7
- style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
+ options[:class] << " bg#{bg_key}"
- content_tag(:div, class: options[:class], style: style) do
+ content_tag(:div, class: options[:class]) do
project.name[0, 1].upcase
end
end
diff --git a/spec/javascripts/avatar_helper_spec.js b/spec/javascripts/avatar_helper_spec.js
new file mode 100644
index 00000000000..b2f80678ae7
--- /dev/null
+++ b/spec/javascripts/avatar_helper_spec.js
@@ -0,0 +1,98 @@
+import { TEST_HOST } from 'spec/test_constants';
+import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
+import {
+ DEFAULT_SIZE_CLASS,
+ IDENTICON_BG_COUNT,
+ renderAvatar,
+ renderIdenticon,
+ getIdenticonBackgroundClass,
+ getIdenticonTitle,
+} from '~/helpers/avatar_helper';
+
+function matchAll(str) {
+ return new RegExp(`^${str}$`);
+}
+
+describe('avatar_helper', () => {
+ describe('getIdenticonBackgroundClass', () => {
+ it('returns identicon bg class from id', () => {
+ expect(getIdenticonBackgroundClass(1)).toEqual('bg2');
+ });
+
+ it(`wraps around if id is bigger than ${IDENTICON_BG_COUNT}`, () => {
+ expect(getIdenticonBackgroundClass(IDENTICON_BG_COUNT + 4)).toEqual('bg5');
+ expect(getIdenticonBackgroundClass((IDENTICON_BG_COUNT * 5) + 6)).toEqual('bg7');
+ });
+ });
+
+ describe('getIdenticonTitle', () => {
+ it('returns identicon title from name', () => {
+ expect(getIdenticonTitle('Lorem')).toEqual('L');
+ expect(getIdenticonTitle('dolar-sit-amit')).toEqual('D');
+ expect(getIdenticonTitle('%-with-special-chars')).toEqual('%');
+ });
+
+ it('returns space if name is falsey', () => {
+ expect(getIdenticonTitle('')).toEqual(' ');
+ expect(getIdenticonTitle(null)).toEqual(' ');
+ });
+ });
+
+ describe('renderIdenticon', () => {
+ it('renders with the first letter as title and bg based on id', () => {
+ const entity = {
+ id: IDENTICON_BG_COUNT + 3,
+ name: 'Xavior',
+ };
+ const options = {
+ sizeClass: 's32',
+ };
+
+ const result = renderIdenticon(entity, options);
+
+ expect(result).toHaveClass(`identicon ${options.sizeClass} bg4`);
+ expect(result).toHaveText(matchAll(getFirstCharacterCapitalized(entity.name)));
+ });
+
+ it('renders with defaults, if no options are given', () => {
+ const entity = {
+ id: 1,
+ name: 'tanuki',
+ };
+
+ const result = renderIdenticon(entity);
+
+ expect(result).toHaveClass(`identicon ${DEFAULT_SIZE_CLASS} bg2`);
+ expect(result).toHaveText(matchAll(getFirstCharacterCapitalized(entity.name)));
+ });
+ });
+
+ describe('renderAvatar', () => {
+ it('renders an image with the avatarUrl', () => {
+ const avatarUrl = `${TEST_HOST}/not-real-assets/test.png`;
+
+ const result = renderAvatar({
+ avatar_url: avatarUrl,
+ });
+
+ expect(result).toBeMatchedBy('img');
+ expect(result).toHaveAttr('src', avatarUrl);
+ expect(result).toHaveClass(DEFAULT_SIZE_CLASS);
+ });
+
+ it('renders an identicon if no avatarUrl', () => {
+ const entity = {
+ id: 1,
+ name: 'walrus',
+ };
+ const options = {
+ sizeClass: 's16',
+ };
+
+ const result = renderAvatar(entity, options);
+
+ expect(result).toHaveClass(`identicon ${options.sizeClass} bg2`);
+ expect(result).toHaveText(matchAll(getFirstCharacterCapitalized(entity.name)));
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 33987574f00..d60485b1308 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -112,4 +112,21 @@ describe('text_utility', () => {
expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World');
});
});
+
+ describe('getFirstCharacterCapitalized', () => {
+ it('returns the first character captialized, if first character is alphabetic', () => {
+ expect(textUtils.getFirstCharacterCapitalized('loremIpsumDolar')).toEqual('L');
+ expect(textUtils.getFirstCharacterCapitalized('Sit amit !')).toEqual('S');
+ });
+
+ it('returns the first character, if first character is non-alphabetic', () => {
+ expect(textUtils.getFirstCharacterCapitalized(' lorem')).toEqual(' ');
+ expect(textUtils.getFirstCharacterCapitalized('%#!')).toEqual('%');
+ });
+
+ it('returns an empty string, if string is falsey', () => {
+ expect(textUtils.getFirstCharacterCapitalized('')).toEqual('');
+ expect(textUtils.getFirstCharacterCapitalized(null)).toEqual('');
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/identicon_spec.js b/spec/javascripts/vue_shared/components/identicon_spec.js
index 647680f00f7..0719800c682 100644
--- a/spec/javascripts/vue_shared/components/identicon_spec.js
+++ b/spec/javascripts/vue_shared/components/identicon_spec.js
@@ -25,19 +25,12 @@ describe('IdenticonComponent', () => {
vm.$destroy();
});
- describe('identiconStyles', () => {
- it('should return styles attribute value with `background-color` property', () => {
+ describe('identiconBackgroundClass', () => {
+ it('should return bg class based on entityId', () => {
vm.entityId = 4;
- expect(vm.identiconStyles).toBeDefined();
- expect(vm.identiconStyles.indexOf('background-color: #E0F2F1;') > -1).toBeTruthy();
- });
-
- it('should return styles attribute value with `color` property', () => {
- vm.entityId = 4;
-
- expect(vm.identiconStyles).toBeDefined();
- expect(vm.identiconStyles.indexOf('color: #555;') > -1).toBeTruthy();
+ expect(vm.identiconBackgroundClass).toBeDefined();
+ expect(vm.identiconBackgroundClass).toBe('bg5');
});
});
@@ -58,7 +51,7 @@ describe('IdenticonComponent', () => {
expect(vm.$el.nodeName).toBe('DIV');
expect(vm.$el.classList.contains('identicon')).toBeTruthy();
expect(vm.$el.classList.contains('s40')).toBeTruthy();
- expect(vm.$el.getAttribute('style').indexOf('background-color') > -1).toBeTruthy();
+ expect(vm.$el.classList.contains('bg2')).toBeTruthy();
vm.$destroy();
});