mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
2a978c4313
Summary: Also fixes a bug that prevented drag and drop from working in BillingLogoEditor, and tweaks the hover state of AppHeader to only activate when the cursor is over the dropdown menu, and not the logo. Test Plan: Manual. 1. Wide logo tests with url http://localhost:8080/o/docs?__themeOrg=fieldlink 2. Avatar on home screen for personal site 3. Avatar on doc screen for personal site 4. Avatar on doc screen with hidden left drawer on doc page 5. Same tests but with personal image 6. Same tests but with custom logo uploaded 7. All above tests but with dark theme 8. All above but on mobile. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D4378
157 lines
3.8 KiB
TypeScript
157 lines
3.8 KiB
TypeScript
import {colors, theme} from 'app/client/ui2018/cssVars';
|
|
import {UserProfile} from 'app/common/LoginSessionAPI';
|
|
import {dom, DomElementArg, styled} from 'grainjs';
|
|
import {icon} from 'app/client/ui2018/icons';
|
|
|
|
export type Size = 'small' | 'medium' | 'large';
|
|
|
|
/**
|
|
* Returns a DOM element showing a circular icon with a user's picture, or the user's initials if
|
|
* picture is missing. Also varies the color of the circle when using initials.
|
|
*/
|
|
export function createUserImage(
|
|
user: Partial<UserProfile>|'exampleUser'|null, size: Size, ...args: DomElementArg[]
|
|
): HTMLElement {
|
|
return cssUserImage(
|
|
cssUserImage.cls('-' + size),
|
|
...(function*() {
|
|
if (user === 'exampleUser') {
|
|
yield [cssUserImage.cls('-example'), cssExampleUserIcon('EyeShow')];
|
|
} else if (!user || user.anonymous) {
|
|
yield cssUserImage.cls('-anon');
|
|
} else {
|
|
if (user.picture) {
|
|
yield cssUserPicture({src: user.picture}, dom.on('error', (ev, el) => dom.hideElem(el, true)));
|
|
}
|
|
yield dom.style('background-color', pickColor(user));
|
|
const initials = getInitials(user);
|
|
if (initials.length > 1) {
|
|
yield cssUserImage.cls('-reduced');
|
|
}
|
|
yield initials;
|
|
}
|
|
})(),
|
|
...args,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extracts initials from a user, e.g. a FullUser. E.g. "Foo Bar" is turned into "FB", and
|
|
* "foo@example.com" into just "f".
|
|
*
|
|
* Exported for testing.
|
|
*/
|
|
export function getInitials(user: Partial<UserProfile>) {
|
|
const source = (user.name && user.name.trim()) || (user.email && user.email.trim()) || '';
|
|
return source.split(/\s+/, 2).map(p => p.slice(0, 1)).join('');
|
|
}
|
|
|
|
/**
|
|
* Hashes the username to return a color.
|
|
*/
|
|
function pickColor(user: Partial<UserProfile>): string {
|
|
let c = hashCode(user.name + ':' + user.email) % someColors.length;
|
|
if (c < 0) { c += someColors.length; }
|
|
return someColors[c];
|
|
}
|
|
|
|
/**
|
|
* Hash a string into an integer. From https://stackoverflow.com/a/7616484/328565.
|
|
*/
|
|
function hashCode(str: string): number {
|
|
let hash: number = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
// tslint:disable-next-line:no-bitwise
|
|
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
// These mostly come from https://clrs.cc/
|
|
const someColors = [
|
|
'#0B437D',
|
|
'#0074D9',
|
|
'#7FDBFF',
|
|
'#39CCCC',
|
|
'#16DD6D',
|
|
'#2ECC40',
|
|
'#16B378',
|
|
'#EFCC00',
|
|
'#FF851B',
|
|
'#FF4136',
|
|
'#85144b',
|
|
'#F012BE',
|
|
'#B10DC9',
|
|
];
|
|
|
|
export const cssUserImage = styled('div', `
|
|
position: relative;
|
|
text-align: center;
|
|
text-transform: uppercase;
|
|
user-select: none;
|
|
-moz-user-select: none;
|
|
color: white;
|
|
border-radius: 100px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
--border-size: 0px;
|
|
width: calc(var(--icon-size, 24px) - var(--border-size));
|
|
height: calc(var(--icon-size, 24px) - var(--border-size));
|
|
line-height: 1em;
|
|
|
|
&-small {
|
|
--icon-size: 24px;
|
|
font-size: 13.5px;
|
|
--reduced-font-size: 12px;
|
|
}
|
|
&-medium {
|
|
--icon-size: 32px;
|
|
font-size: 18px;
|
|
--reduced-font-size: 16px;
|
|
}
|
|
&-border {
|
|
--border-size: 2px;
|
|
}
|
|
&-large {
|
|
--icon-size: 40px;
|
|
font-size: 22.5px;
|
|
--reduced-font-size: 20px;
|
|
}
|
|
|
|
&-anon {
|
|
border: 1px solid ${colors.slate};
|
|
color: ${colors.slate};
|
|
}
|
|
&-anon::before {
|
|
content: "?"
|
|
}
|
|
&-reduced {
|
|
font-size: var(--reduced-font-size);
|
|
}
|
|
&-square {
|
|
border-radius: 0px;
|
|
}
|
|
&-example {
|
|
background-color: ${colors.slate};
|
|
border: 1px solid ${colors.slate};
|
|
}
|
|
`);
|
|
|
|
const cssUserPicture = styled('img', `
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
background-color: ${theme.menuBg};
|
|
border-radius: inherit;
|
|
box-sizing: content-box; /* keep the border outside of the size of the image */
|
|
`);
|
|
|
|
const cssExampleUserIcon = styled(icon, `
|
|
background-color: white;
|
|
width: 45px;
|
|
height: 45px;
|
|
transform: scaleY(0.75);
|
|
`);
|