20 changed files with 2560 additions and 2789 deletions
@ -27,65 +27,64 @@ |
|||||
"homepage": "https://meshtastic.org", |
"homepage": "https://meshtastic.org", |
||||
"dependencies": { |
"dependencies": { |
||||
"@bufbuild/protobuf": "^1.10.0", |
"@bufbuild/protobuf": "^1.10.0", |
||||
"@emeraldpay/hashicon-react": "^0.5.2", |
|
||||
"@meshtastic/js": "2.3.7-5", |
"@meshtastic/js": "2.3.7-5", |
||||
"@noble/curves": "^1.5.0", |
"@noble/curves": "^1.8.1", |
||||
"@radix-ui/react-accordion": "^1.2.0", |
"@radix-ui/react-accordion": "^1.2.2", |
||||
"@radix-ui/react-checkbox": "^1.1.0", |
"@radix-ui/react-checkbox": "^1.1.3", |
||||
"@radix-ui/react-dialog": "^1.1.1", |
"@radix-ui/react-dialog": "^1.1.5", |
||||
"@radix-ui/react-dropdown-menu": "^2.1.1", |
"@radix-ui/react-dropdown-menu": "^2.1.5", |
||||
"@radix-ui/react-label": "^2.1.0", |
"@radix-ui/react-label": "^2.1.1", |
||||
"@radix-ui/react-menubar": "^1.1.1", |
"@radix-ui/react-menubar": "^1.1.5", |
||||
"@radix-ui/react-popover": "^1.1.1", |
"@radix-ui/react-popover": "^1.1.5", |
||||
"@radix-ui/react-scroll-area": "^1.1.0", |
"@radix-ui/react-scroll-area": "^1.2.2", |
||||
"@radix-ui/react-select": "^2.1.1", |
"@radix-ui/react-select": "^2.1.5", |
||||
"@radix-ui/react-separator": "^1.1.0", |
"@radix-ui/react-separator": "^1.1.1", |
||||
"@radix-ui/react-switch": "^1.1.0", |
"@radix-ui/react-switch": "^1.1.2", |
||||
"@radix-ui/react-tabs": "^1.1.0", |
"@radix-ui/react-tabs": "^1.1.2", |
||||
"@radix-ui/react-toast": "^1.2.1", |
"@radix-ui/react-toast": "^1.2.5", |
||||
"@radix-ui/react-tooltip": "^1.1.1", |
"@radix-ui/react-tooltip": "^1.1.7", |
||||
"@turf/turf": "^6.5.0", |
"@turf/turf": "^7.2.0", |
||||
"base64-js": "^1.5.1", |
"base64-js": "^1.5.1", |
||||
"class-validator": "^0.14.1", |
"class-validator": "^0.14.1", |
||||
"class-variance-authority": "^0.7.0", |
"class-variance-authority": "^0.7.1", |
||||
"clsx": "^2.1.1", |
"clsx": "^2.1.1", |
||||
"cmdk": "^1.0.0", |
"cmdk": "^1.0.4", |
||||
"crypto-random-string": "^5.0.0", |
"crypto-random-string": "^5.0.0", |
||||
"immer": "^10.1.1", |
"immer": "^10.1.1", |
||||
"js-cookie": "^3.0.5", |
"js-cookie": "^3.0.5", |
||||
"lucide-react": "^0.363.0", |
"lucide-react": "^0.474.0", |
||||
"mapbox-gl": "^3.6.0", |
"mapbox-gl": "^3.9.4", |
||||
"maplibre-gl": "4.1.2", |
"maplibre-gl": "4.1.2", |
||||
"react": "^18.3.1", |
"react": "^19.0.0", |
||||
"react-dom": "^18.3.1", |
"react-dom": "^19.0.0", |
||||
"react-hook-form": "^7.52.0", |
"react-hook-form": "^7.54.2", |
||||
"react-map-gl": "7.1.7", |
"react-map-gl": "7.1.9", |
||||
"react-qrcode-logo": "^2.10.0", |
"react-qrcode-logo": "^3.0.0", |
||||
"rfc4648": "^1.5.3", |
"rfc4648": "^1.5.4", |
||||
"timeago-react": "^3.0.6", |
"timeago-react": "^3.0.6", |
||||
"vite-plugin-node-polyfills": "^0.22.0", |
"vite-plugin-node-polyfills": "^0.23.0", |
||||
"zustand": "4.5.2" |
"zustand": "5.0.3" |
||||
}, |
}, |
||||
"devDependencies": { |
"devDependencies": { |
||||
"@biomejs/biome": "^1.8.2", |
"@biomejs/biome": "^1.9.4", |
||||
"@rsbuild/core": "^1.0.10", |
"@rsbuild/core": "^1.2.3", |
||||
"@rsbuild/plugin-react": "^1.0.3", |
"@rsbuild/plugin-react": "^1.1.0", |
||||
"@types/chrome": "^0.0.263", |
"@types/chrome": "^0.0.299", |
||||
"@types/js-cookie": "^3.0.6", |
"@types/js-cookie": "^3.0.6", |
||||
"@types/node": "^20.14.9", |
"@types/node": "^22.12.0", |
||||
"@types/react": "^18.3.3", |
"@types/react": "^19.0.8", |
||||
"@types/react-dom": "^18.3.0", |
"@types/react-dom": "^19.0.3", |
||||
"@types/w3c-web-serial": "^1.0.6", |
"@types/w3c-web-serial": "^1.0.7", |
||||
"@types/web-bluetooth": "^0.0.20", |
"@types/web-bluetooth": "^0.0.20", |
||||
"autoprefixer": "^10.4.19", |
"autoprefixer": "^10.4.20", |
||||
"gzipper": "^7.2.0", |
"gzipper": "^8.2.0", |
||||
"postcss": "^8.4.38", |
"postcss": "^8.5.1", |
||||
"simple-git-hooks": "^2.11.1", |
"simple-git-hooks": "^2.11.1", |
||||
"tailwind-merge": "^2.3.0", |
"tailwind-merge": "^2.6.0", |
||||
"tailwindcss": "^3.4.4", |
"tailwindcss": "^3.4.17", |
||||
"tailwindcss-animate": "^1.0.7", |
"tailwindcss-animate": "^1.0.7", |
||||
"tar": "^6.2.1", |
"tar": "^7.4.3", |
||||
"typescript": "^5.5.2" |
"typescript": "^5.7.3" |
||||
}, |
}, |
||||
"packageManager": "[email protected]" |
"packageManager": "[email protected]" |
||||
} |
} |
||||
|
|||||
File diff suppressed because it is too large
@ -0,0 +1,94 @@ |
|||||
|
import { cn } from "@app/core/utils/cn"; |
||||
|
import type React from "react"; |
||||
|
|
||||
|
type RGBColor = { |
||||
|
r: number; |
||||
|
g: number; |
||||
|
b: number; |
||||
|
a: number; |
||||
|
}; |
||||
|
|
||||
|
interface AvatarProps { |
||||
|
text: string; |
||||
|
size?: "sm" | "lg"; |
||||
|
className?: string; |
||||
|
} |
||||
|
|
||||
|
// biome-ignore lint/complexity/noStaticOnlyClass: stop being annoying Biome
|
||||
|
class ColorUtils { |
||||
|
static hexToRgb(hex: number): RGBColor { |
||||
|
return { |
||||
|
r: (hex & 0xff0000) >> 16, |
||||
|
g: (hex & 0x00ff00) >> 8, |
||||
|
b: hex & 0x0000ff, |
||||
|
a: 255, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static rgbToHex(color: RGBColor): number { |
||||
|
return ( |
||||
|
(Math.round(color.a) << 24) | |
||||
|
(Math.round(color.r) << 16) | |
||||
|
(Math.round(color.g) << 8) | |
||||
|
Math.round(color.b) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
static isLight(color: RGBColor): boolean { |
||||
|
const brightness = (color.r * 299 + color.g * 587 + color.b * 114) / 1000; |
||||
|
return brightness > 127.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const Avatar: React.FC<AvatarProps> = ({ |
||||
|
text, |
||||
|
size = "sm", |
||||
|
className, |
||||
|
}) => { |
||||
|
const sizes = { |
||||
|
sm: "size-11 text-xs", |
||||
|
lg: "size-16 text-lg", |
||||
|
}; |
||||
|
|
||||
|
// Pick a color based on the text provided to function
|
||||
|
const getColorFromText = (text: string): RGBColor => { |
||||
|
let hash = 0; |
||||
|
for (let i = 0; i < text.length; i++) { |
||||
|
hash = text.charCodeAt(i) + ((hash << 5) - hash); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
r: (hash & 0xff0000) >> 16, |
||||
|
g: (hash & 0x00ff00) >> 8, |
||||
|
b: hash & 0x0000ff, |
||||
|
a: 255, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
const bgColor = getColorFromText(text ?? "UNK"); |
||||
|
const isLight = ColorUtils.isLight(bgColor); |
||||
|
const textColor = isLight ? "#000000" : "#FFFFFF"; |
||||
|
const initials = text?.toUpperCase().slice(0, 4) ?? "UNK"; |
||||
|
|
||||
|
return ( |
||||
|
<div |
||||
|
className={cn( |
||||
|
` |
||||
|
rounded-full |
||||
|
flex |
||||
|
items-center |
||||
|
justify-center |
||||
|
size-11 |
||||
|
font-semibold`,
|
||||
|
sizes[size], |
||||
|
className, |
||||
|
)} |
||||
|
style={{ |
||||
|
backgroundColor: `rgb(${bgColor.r}, ${bgColor.g}, ${bgColor.b})`, |
||||
|
color: textColor, |
||||
|
}} |
||||
|
> |
||||
|
<p className="p-1">{initials}</p> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
Loading…
Reference in new issue