From 64c2694ecb2f7ae3b912460af584ac5b1b1a4eef Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 12 Apr 2024 16:35:53 -0500 Subject: [PATCH 1/3] copy identicon into the repo --- app/ui/lib/Identicon.tsx | 53 +++++++++++++++++++++++++++++++++++++++- package-lock.json | 31 +++++++++++------------ package.json | 3 ++- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/app/ui/lib/Identicon.tsx b/app/ui/lib/Identicon.tsx index 776762d6a4..592dd7a07b 100644 --- a/app/ui/lib/Identicon.tsx +++ b/app/ui/lib/Identicon.tsx @@ -5,9 +5,60 @@ * * Copyright Oxide Computer Company */ +import md5 from 'md5' import { useMemo } from 'react' -import { generateIdenticon, md5 } from '@oxide/identicon' +const generateIdenticon = (str: string): string => { + const pixels = renderPixels(md5(str)) + return pixelsToSvg(pixels) +} + +type Rectangle = { + x: number + y: number + isPixel: boolean +} + +const renderPixels = (hash: string) => { + const buffer: Rectangle[] = [] + + for (let i = 0; i < 18; i++) { + const isPixel = hash.charCodeAt(i) % 2 === 0 + + if (i < 3) { + // Start with the two central columns + buffer.push({ x: 2, y: i, isPixel: isPixel }) + buffer.push({ x: 3, y: i, isPixel: isPixel }) + } else if (i < 6) { + // Move out to the columns one from the edge + buffer.push({ x: 1, y: i - 3, isPixel: isPixel }) + buffer.push({ x: 4, y: i - 3, isPixel: isPixel }) + } else if (i < 9) { + // Fill the outside columns + buffer.push({ x: 0, y: i - 6, isPixel: isPixel }) + buffer.push({ x: 5, y: i - 6, isPixel: isPixel }) + } + } + + return buffer +} + +const pixelsToSvg = (pixels: Rectangle[]): string => { + let xml = ` + ` + + pixels.forEach((pixel) => { + if (!pixel.isPixel) return + + const x = pixel.x * 3 + 2 * pixel.x + const y = pixel.y * 8 + 2 * pixel.y + xml += `` + }) + + xml += `` + + return xml +} type IdenticonProps = { /** string used to generate the graphic */ diff --git a/package-lock.json b/package-lock.json index a6c88ca362..2d7bd05704 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@floating-ui/react": "^0.26.11", "@headlessui/react": "^1.7.18", "@oxide/design-system": "^1.2.10", - "@oxide/identicon": "0.0.4", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", @@ -28,6 +27,7 @@ "filesize": "^10.1.1", "lodash.throttle": "^4.1.1", "match-sorter": "^6.3.4", + "md5": "^2.3.0", "mousetrap": "^1.6.5", "p-map": "^7.0.2", "p-retry": "^6.2.0", @@ -61,6 +61,7 @@ "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.2", "@types/lodash.throttle": "^4.1.9", + "@types/md5": "^2.3.5", "@types/mousetrap": "^1.6.15", "@types/react": "^18.2.74", "@types/react-dom": "^18.2.24", @@ -2036,14 +2037,6 @@ "react-dom": ">=16.8.0" } }, - "node_modules/@oxide/identicon": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@oxide/identicon/-/identicon-0.0.4.tgz", - "integrity": "sha512-LcjQCjBo+XABZEidOpQAYYa/CUA8FaJZrb/JK1HGkPKzWAx94O+zFmnmxTWqvl7VGOEGyYObNxB43v3BCVe3iw==", - "dependencies": { - "md5": "^2.3.0" - } - }, "node_modules/@oxide/react-asciidoc": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@oxide/react-asciidoc/-/react-asciidoc-0.2.3.tgz", @@ -5573,6 +5566,12 @@ "@types/lodash": "*" } }, + "node_modules/@types/md5": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.3.5.tgz", + "integrity": "sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==", + "dev": true + }, "node_modules/@types/mdast": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", @@ -20992,14 +20991,6 @@ } } }, - "@oxide/identicon": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@oxide/identicon/-/identicon-0.0.4.tgz", - "integrity": "sha512-LcjQCjBo+XABZEidOpQAYYa/CUA8FaJZrb/JK1HGkPKzWAx94O+zFmnmxTWqvl7VGOEGyYObNxB43v3BCVe3iw==", - "requires": { - "md5": "^2.3.0" - } - }, "@oxide/react-asciidoc": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@oxide/react-asciidoc/-/react-asciidoc-0.2.3.tgz", @@ -23356,6 +23347,12 @@ "@types/lodash": "*" } }, + "@types/md5": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.3.5.tgz", + "integrity": "sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==", + "dev": true + }, "@types/mdast": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", diff --git a/package.json b/package.json index 5c9b3449f8..d2eb36c924 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "@floating-ui/react": "^0.26.11", "@headlessui/react": "^1.7.18", "@oxide/design-system": "^1.2.10", - "@oxide/identicon": "0.0.4", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", @@ -48,6 +47,7 @@ "filesize": "^10.1.1", "lodash.throttle": "^4.1.1", "match-sorter": "^6.3.4", + "md5": "^2.3.0", "mousetrap": "^1.6.5", "p-map": "^7.0.2", "p-retry": "^6.2.0", @@ -81,6 +81,7 @@ "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.2", "@types/lodash.throttle": "^4.1.9", + "@types/md5": "^2.3.5", "@types/mousetrap": "^1.6.15", "@types/react": "^18.2.74", "@types/react-dom": "^18.2.24", From 92769ec6ff018d61ac083e7e4ad53792079676f6 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 12 Apr 2024 16:45:30 -0500 Subject: [PATCH 2/3] render svg directly --- app/ui/lib/Identicon.tsx | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/app/ui/lib/Identicon.tsx b/app/ui/lib/Identicon.tsx index 592dd7a07b..82407c5f7f 100644 --- a/app/ui/lib/Identicon.tsx +++ b/app/ui/lib/Identicon.tsx @@ -8,11 +8,6 @@ import md5 from 'md5' import { useMemo } from 'react' -const generateIdenticon = (str: string): string => { - const pixels = renderPixels(md5(str)) - return pixelsToSvg(pixels) -} - type Rectangle = { x: number y: number @@ -43,21 +38,12 @@ const renderPixels = (hash: string) => { return buffer } -const pixelsToSvg = (pixels: Rectangle[]): string => { - let xml = ` - ` - - pixels.forEach((pixel) => { - if (!pixel.isPixel) return - - const x = pixel.x * 3 + 2 * pixel.x - const y = pixel.y * 8 + 2 * pixel.y - xml += `` - }) - - xml += `` +function Pixel(pixel: Rectangle) { + if (!pixel.isPixel) return null - return xml + const x = pixel.x * 3 + 2 * pixel.x + const y = pixel.y * 8 + 2 * pixel.y + return } type IdenticonProps = { @@ -67,6 +53,16 @@ type IdenticonProps = { } export function Identicon({ name, className }: IdenticonProps) { - const content = useMemo(() => generateIdenticon(md5(name)), [name]) - return
+ const pixels = useMemo(() => renderPixels(md5(name)), [name]) + return ( +
+ + + {pixels.map((pixel) => ( + + ))} + + +
+ ) } From 112671595ca9aed1be1020a6401abea730a4ea1e Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 12 Apr 2024 16:54:22 -0500 Subject: [PATCH 3/3] clean it up --- app/ui/lib/Identicon.tsx | 42 ++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/app/ui/lib/Identicon.tsx b/app/ui/lib/Identicon.tsx index 82407c5f7f..e09b5f6616 100644 --- a/app/ui/lib/Identicon.tsx +++ b/app/ui/lib/Identicon.tsx @@ -8,44 +8,33 @@ import md5 from 'md5' import { useMemo } from 'react' -type Rectangle = { - x: number - y: number - isPixel: boolean -} +type Rectangle = { x: number; y: number; isOn: boolean } -const renderPixels = (hash: string) => { +const getPixels = (s: string) => { + const hash = md5(s) const buffer: Rectangle[] = [] for (let i = 0; i < 18; i++) { - const isPixel = hash.charCodeAt(i) % 2 === 0 + const isOn = hash.charCodeAt(i) % 2 === 0 if (i < 3) { // Start with the two central columns - buffer.push({ x: 2, y: i, isPixel: isPixel }) - buffer.push({ x: 3, y: i, isPixel: isPixel }) + buffer.push({ x: 2, y: i, isOn }) + buffer.push({ x: 3, y: i, isOn }) } else if (i < 6) { // Move out to the columns one from the edge - buffer.push({ x: 1, y: i - 3, isPixel: isPixel }) - buffer.push({ x: 4, y: i - 3, isPixel: isPixel }) + buffer.push({ x: 1, y: i - 3, isOn }) + buffer.push({ x: 4, y: i - 3, isOn }) } else if (i < 9) { // Fill the outside columns - buffer.push({ x: 0, y: i - 6, isPixel: isPixel }) - buffer.push({ x: 5, y: i - 6, isPixel: isPixel }) + buffer.push({ x: 0, y: i - 6, isOn }) + buffer.push({ x: 5, y: i - 6, isOn }) } } return buffer } -function Pixel(pixel: Rectangle) { - if (!pixel.isPixel) return null - - const x = pixel.x * 3 + 2 * pixel.x - const y = pixel.y * 8 + 2 * pixel.y - return -} - type IdenticonProps = { /** string used to generate the graphic */ name: string @@ -53,14 +42,17 @@ type IdenticonProps = { } export function Identicon({ name, className }: IdenticonProps) { - const pixels = useMemo(() => renderPixels(md5(name)), [name]) + const pixels = useMemo(() => getPixels(name), [name]) return (
- {pixels.map((pixel) => ( - - ))} + {pixels.map((pixel) => { + if (!pixel.isOn) return null + const x = pixel.x * 3 + 2 * pixel.x + const y = pixel.y * 8 + 2 * pixel.y + return + })}