Updated avatar colors and events table.

This commit is contained in:
Mike Cao 2024-08-01 22:53:17 -07:00
parent 9c32057841
commit 3262ea0285
6 changed files with 77 additions and 116 deletions

View File

@ -2,7 +2,7 @@ import { GridTable, GridColumn } from 'react-basics';
import { useLocale, useMessages } from 'components/hooks';
import Empty from 'components/common/Empty';
import { formatDistance } from 'date-fns';
import Profile from 'components/common/Profile';
import Avatar from 'components/common/Avatar';
import Link from 'next/link';
export function EventsTable({ data = [] }) {
@ -15,18 +15,23 @@ export function EventsTable({ data = [] }) {
return (
<GridTable data={data}>
<GridColumn name="id" label="ID" />
<GridColumn name="session" label={formatMessage(labels.session)}>
<GridColumn name="session" label={formatMessage(labels.session)} width={'100px'}>
{row => (
<Link href={`/sessions/`}>
<Profile seed={row.sessionId} size={64} />
<Avatar seed={row.sessionId} size={64} />
</Link>
)}
</GridColumn>
<GridColumn name="eventName" label={formatMessage(labels.event)}>
{row => formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)}
<GridColumn name="event" label={formatMessage(labels.event)}>
{row => {
return (
<>
{formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)}
<strong>{row.eventName}</strong>
</>
);
}}
</GridColumn>
<GridColumn name="eventName" label={formatMessage(labels.name)} />
<GridColumn name="urlPath" label={formatMessage(labels.path)} />
<GridColumn name="created" label={formatMessage(labels.created)}>
{row =>

View File

@ -1,7 +1,7 @@
import Link from 'next/link';
import { GridColumn, GridTable, useBreakpoint } from 'react-basics';
import { useFormat, useLocale, useMessages } from 'components/hooks';
import Profile from 'components/common/Profile';
import Avatar from 'components/common/Avatar';
import styles from './SessionsTable.module.css';
import { formatDate } from 'lib/date';
@ -16,7 +16,7 @@ export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean
<GridColumn name="id" label="ID" width="300px">
{row => (
<Link href={`sessions/${row.id}`} className={styles.link}>
<Profile key={row.id} seed={row.id} size={64} />
<Avatar key={row.id} seed={row.id} size={64} />
{row.id}
</Link>
)}

View File

@ -3,7 +3,7 @@ import WebsiteHeader from '../../WebsiteHeader';
import SessionInfo from './SessionInfo';
import { useWebsiteSession } from 'components/hooks';
import { Loading } from 'react-basics';
import Profile from 'components/common/Profile';
import Avatar from 'components/common/Avatar';
import { SessionActivity } from './SessionActivity';
import { SessionStats } from './SessionStats';
import { SessionData } from './SessionData';
@ -27,7 +27,7 @@ export default function SessionDetailsPage({
<WebsiteHeader websiteId={websiteId} />
<div className={styles.page}>
<div className={styles.sidebar}>
<Profile seed={data?.id} />
<Avatar seed={data?.id} />
<SessionInfo data={data} />
</div>
<div className={styles.content}>

View File

@ -1,71 +1,23 @@
import md5 from 'md5';
import { colord, extend } from 'colord';
import harmoniesPlugin from 'colord/plugins/harmonies';
import mixPlugin from 'colord/plugins/mix';
import { useMemo } from 'react';
import { createAvatar } from '@dicebear/core';
import { lorelei } from '@dicebear/collection';
import { getColor, getPastel } from 'lib/colors';
extend([harmoniesPlugin, mixPlugin]);
const lib = lorelei;
const harmonies = [
//'analogous',
//'complementary',
'double-split-complementary',
//'rectangle',
'split-complementary',
'tetradic',
//'triadic',
];
function Avatar({ seed, size = 128, ...props }: { seed: string; size?: number }) {
const backgroundColor = getPastel(getColor(seed), 4);
const color = (value: string, invert: boolean = false) => {
const c = colord(value.startsWith('#') ? value : `#${value}`);
const avatar = useMemo(() => {
return createAvatar(lib, {
...props,
seed,
size,
backgroundColor: [backgroundColor],
}).toDataUri();
}, []);
if (invert && c.isDark()) {
return c.invert();
}
return c;
};
const remix = (hash: string) => {
const a = hash.substring(0, 6);
const b = hash.substring(6, 12);
const c = hash.substring(12, 18);
const d = hash.substring(18, 24);
const e = hash.substring(24, 30);
const f = hash.substring(30, 32);
const base = [b, c, d, e]
.reduce((acc, val) => {
return acc.mix(color(val), 0.05);
}, color(a))
.saturate(0.1)
.toHex();
const harmony = pick(parseInt(f, 16), harmonies);
return color(base, true)
.harmonies(harmony)
.map(c => c.toHex());
};
const pick = (num: number, arr: any[]) => {
return arr[num % arr.length];
};
export function Avatar({ value }: { value: string }) {
const hash = md5(value);
const colors = remix(hash);
return (
<svg viewBox="0 0 100 100">
<defs>
<linearGradient id={`color-${hash}`} gradientTransform="rotate(90)">
<stop offset="0%" stopColor={colors[1]} />
<stop offset="100%" stopColor={colors[2]} />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="50" fill={`url(#color-${hash})`} />
</svg>
);
return <img src={avatar} alt="Avatar" style={{ borderRadius: '100%' }} />;
}
export default Avatar;

View File

@ -1,41 +0,0 @@
import { useMemo } from 'react';
import { createAvatar } from '@dicebear/core';
import { lorelei } from '@dicebear/collection';
import md5 from 'md5';
const lib = lorelei;
function convertToPastel(hexColor: string, pastelFactor: number = 0.5) {
// Remove the # if present
hexColor = hexColor.replace(/^#/, '');
// Convert hex to RGB
let r = parseInt(hexColor.substring(0, 2), 16);
let g = parseInt(hexColor.substring(2, 4), 16);
let b = parseInt(hexColor.substring(4, 6), 16);
// Calculate pastel version (mix with white)
//const pastelFactor = 0.5; // Adjust this value to control pastel intensity
r = Math.floor((r + 255 * pastelFactor) / (1 + pastelFactor));
g = Math.floor((g + 255 * pastelFactor) / (1 + pastelFactor));
b = Math.floor((b + 255 * pastelFactor) / (1 + pastelFactor));
// Convert back to hex
return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`;
}
function Profile({ seed, size = 128, ...props }: { seed: string; size?: number }) {
const avatar = useMemo(() => {
return createAvatar(lib, {
...props,
seed,
size,
backgroundColor: [convertToPastel(md5(seed).substring(0, 6), 2).replace(/^#/, '')],
}).toDataUri();
}, []);
return <img src={avatar} alt="Avatar" style={{ borderRadius: '100%' }} />;
}
export default Profile;

45
src/lib/colors.ts Normal file
View File

@ -0,0 +1,45 @@
import md5 from 'md5';
export const pick = (num: number, arr: any[]) => {
return arr[num % arr.length];
};
export function clamp(num: number, min: number, max: number) {
return num < min ? min : num > max ? max : num;
}
export function hex2RGB(color: string, min: number = 0, max: number = 255) {
const c = color.replace(/^#/, '');
const diff = max - min;
const normalize = (num: number) => {
return Math.floor((num / 255) * diff + min);
};
const r = normalize(parseInt(c.substring(0, 2), 16));
const g = normalize(parseInt(c.substring(2, 4), 16));
const b = normalize(parseInt(c.substring(4, 6), 16));
return { r, g, b };
}
export function rgb2Hex(r: number, g: number, b: number, prefix = '') {
return `${prefix}${r.toString(16)}${g.toString(16)}${b.toString(16)}`;
}
export function getPastel(color: string, factor: number = 0.5, prefix = '') {
let { r, g, b } = hex2RGB(color);
r = Math.floor((r + 255 * factor) / (1 + factor));
g = Math.floor((g + 255 * factor) / (1 + factor));
b = Math.floor((b + 255 * factor) / (1 + factor));
return rgb2Hex(r, g, b, prefix);
}
export function getColor(seed: string, min: number = 0, max: number = 255) {
const color = md5(seed).substring(0, 6);
const { r, g, b } = hex2RGB(color, min, max);
return rgb2Hex(r, g, b);
}