-
Notifications
You must be signed in to change notification settings - Fork 117
/
utils.js
191 lines (158 loc) · 4.62 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
'use strict';
import retina from 'is-retina';
const IS_RETINA = retina();
export
function fetch(url, successCb, errorCb) {
const request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
const data = JSON.parse(request.responseText);
successCb(data);
} else {
errorCb(request.status);
}
}
};
request.open('GET', url, true);
request.send();
}
export
function fetchJSONP(url, successCb, errorCb) {
const callbackName = 'jsonp_cb_' + Math.round(100000 * Math.random());
const script = document.createElement('script');
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
document.body.appendChild(script);
script.onerror = function() {
errorCb();
};
window[callbackName] = function(data) {
delete window[callbackName];
document.body.removeChild(script);
successCb(data);
};
}
// https://webaim.org/resources/contrastchecker/
export
const defaultColors = [
'#A62A21',
'#7e3794',
'#0B51C1',
'#3A6024',
'#A81563',
'#B3003C'
];
// https://regex101.com/r/YEsPER/1
// https://developer.mozilla.org/en-US/docs/Web/CSS/length
const reSize = /^([-+]?(?:\d+(?:\.\d+)?|\.\d+))([a-z]{2,4}|%)?$/;
// https://en.wikipedia.org/wiki/Linear_congruential_generator
function _stringAsciiPRNG(value, m) {
// Xn+1 = (a * Xn + c) % m
// 0 < a < m
// 0 <= c < m
// 0 <= X0 < m
const charCodes = [...value].map(letter => letter.charCodeAt(0));
const len = charCodes.length;
const a = (len % (m - 1)) + 1;
const c = charCodes.reduce((current, next) => current + next) % m;
let random = charCodes[0] % m;
for (let i = 0; i < len; i++)
random = ((a * random) + c) % m;
return random;
}
export
function getRandomColor(value, colors = defaultColors)
{
// if no value is passed, always return transparent color otherwise
// a rerender would show a new color which would will
// give strange effects when an interface is loading
// and gets rerendered a few consequent times
if(!value)
return 'transparent';
// value based random color index
// the reason we don't just use a random number is to make sure that
// a certain value will always get the same color assigned given
// a fixed set of colors
const colorIndex = _stringAsciiPRNG(value, colors.length);
return colors[colorIndex];
}
export
function parseSize(size) {
size = '' + size;
const [,
value = 0,
unit = 'px'
] = reSize.exec(size) || [];
return {
value: parseFloat(value),
str: value + unit,
unit
};
}
/**
* Calculate absolute size in pixels we want for the images
* that get requested from the various sources. They don't
* understand relative sizes like `em` or `vww`. We select
* a fixed size of 512px when we can't detect the true pixel size.
*/
export function getImageSize(size) {
size = parseSize(size);
if (isNaN(size.value)) // invalid size, use fallback
size = 512;
else if (size.unit === 'px') // px are good, use them
size = size.value;
else if (size.value === 0) // relative 0 === absolute 0
size = 0;
else // anything else is unknown, use fallback
size = 512;
if (IS_RETINA)
size = size * 2;
return size;
}
export
function defaultInitials(name, { maxInitials }) {
return name.split(/\s/)
.map(part => part.substring(0, 1).toUpperCase())
.filter(v => !!v)
.slice(0, maxInitials)
.join('')
.toUpperCase();
}
/**
* Grouped timeouts reduce the amount of timeouts trigged
* by grouping multiple handlers into a single setTimeout call.
*
* This reduces accuracy of the timeout but will be less expensive
* when multiple avatar have been loaded into view.
*/
const timeoutGroups = {};
export
function setGroupedTimeout(fn, ttl) {
if (timeoutGroups[ttl]) {
timeoutGroups[ttl].push(fn);
return;
}
const callbacks = timeoutGroups[ttl] = [fn];
setTimeout(() => {
delete timeoutGroups[ttl];
callbacks.forEach(cb => cb());
}, ttl);
}
export
function getNullableText(...args) {
for (const arg of args) {
if (arg || arg === '')
return arg;
if (arg === false || arg === null)
return null;
}
return;
}
export
function calculateBorderRadius(round) {
if (round === true)
return '100%';
if (round === false)
return;
return round;
}