Add vanilla JS avatar_helper and update existing avatar helpers
This commit is contained in:
parent
c1fc33d590
commit
fbfe04401d
9 changed files with 200 additions and 45 deletions
33
app/assets/javascripts/helpers/avatar_helper.js
Normal file
33
app/assets/javascripts/helpers/avatar_helper.js
Normal file
|
@ -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)}" />`;
|
||||
}
|
|
@ -75,6 +75,20 @@ export function capitalizeFirstCharacter(text) {
|
|||
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -486,6 +486,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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
98
spec/javascripts/avatar_helper_spec.js
Normal file
98
spec/javascripts/avatar_helper_spec.js
Normal file
|
@ -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)));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue