diff options
author | Clement Ho <clemmakesapps@gmail.com> | 2018-08-01 19:43:50 +0000 |
---|---|---|
committer | Clement Ho <clemmakesapps@gmail.com> | 2018-08-01 19:43:50 +0000 |
commit | 92e079ede29d4d1e1c4a36166aa76ea13e3412d3 (patch) | |
tree | 1b8b25c8ca0f7e048aed07de9cdf24a44fc58c36 | |
parent | c1fc33d590b3f853ec820fa33ebc114b86af692d (diff) | |
parent | fbfe04401deb7a08da03502282531364aa25d511 (diff) | |
download | gitlab-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.js | 33 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/text_utility.js | 14 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/identicon.vue | 26 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/avatar.scss | 12 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/variables.scss | 12 | ||||
-rw-r--r-- | app/helpers/avatars_helper.rb | 16 | ||||
-rw-r--r-- | spec/javascripts/avatar_helper_spec.js | 98 | ||||
-rw-r--r-- | spec/javascripts/lib/utils/text_utility_spec.js | 17 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/identicon_spec.js | 17 |
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(); }); |