Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement BitIcon and UUIDIcon #438

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[{packages,src,scripts}/**.{ts,json,js,css,tsx,jsx}]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
treemap,
editablelabel,
tab,
biticon,
components,
style-bulma,
]
Expand Down
1 change: 1 addition & 0 deletions examples/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"private": true,
"devDependencies": {
"@kitsuyui/react-binary": "workspace:^",
"@kitsuyui/react-biticon": "workspace:^",
"@kitsuyui/react-clock": "workspace:^",
"@kitsuyui/react-components": "workspace:^",
"@kitsuyui/react-dekamoji": "workspace:^",
Expand Down
17 changes: 17 additions & 0 deletions examples/storybook/stories/biticon/Detailed.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { UUIDIcon } from '@kitsuyui/react-biticon'

import type { Meta, StoryObj } from '@storybook/react'

const meta: Meta<typeof UUIDIcon> = {
title: 'Base/Biticon/Detailed',
component: UUIDIcon,
}

export default meta
type Story = StoryObj<typeof UUIDIcon>

export const Default: Story = {
args: {
uuid: '018fc860-4d10-7b70-b012-9ca7c3525a13',
},
}
15 changes: 15 additions & 0 deletions examples/storybook/stories/biticon/Introduction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# @kitsuyui/react-biticon

import { Canvas, Meta } from '@storybook/blocks'

import * as DetailedStories from './Detailed.stories'
import { BitIcon } from "@kitsuyui/react-biticon";

<Meta title="Base/Biticon/Introduction" />
<Canvas of={DetailedStories.Default} />

As you can see, this package is a React component that displays 128-bit data as an icon.

## Installation

## Usage
17 changes: 17 additions & 0 deletions examples/storybook/stories/biticon/Simple.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { UUIDIcon } from '@kitsuyui/react-biticon'

import type { Meta, StoryObj } from '@storybook/react'

const meta: Meta<typeof UUIDIcon> = {
title: 'Base/Biticon/Simple',
component: UUIDIcon,
}

export default meta
type Story = StoryObj<typeof UUIDIcon>

export const Default: Story = {
args: {
uuid: '018fc860-4d10-7b70-b012-9ca7c3525a13',
},
}
4 changes: 3 additions & 1 deletion examples/storybook/stories/example.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export default meta
type Story = StoryObj<typeof Something>

export const Default: Story = {
args: {},
args: {
bits: 0b10101010,
},
}
4 changes: 4 additions & 0 deletions packages/biticon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# @kitsuyui/react-biticon

Bit icon component for React.
This package is a React component that displays 128-bit data as an icon.
35 changes: 35 additions & 0 deletions packages/biticon/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@kitsuyui/react-biticon",
"version": "0.0.0",
"description": "",
"license": "MIT",
"author": "Yui Kitsu <kitsuyui@kitsuyui.com>",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist",
"package.json"
],
"scripts": {
"build": "tsup --config ../../tsup.config.mjs --clean",
"dev": "tsup --config ../../tsup.config.mjs --watch"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
}
}
84 changes: 84 additions & 0 deletions packages/biticon/src/base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react'
import { COLORS } from './colors'
import { applySBox128bitBigintTo128bitBigint } from './bitShuffle'


export const UUIDIcon = (props: { uuid: string }): JSX.Element => {
const uuid = props.uuid
if (!isValidUUID(uuid)) {
return <span>Invalid UUID</span>
}
const bits = uuidToBigInt(uuid)
const newBits = applySBox128bitBigintTo128bitBigint(bits)
return <BitIcon bits={newBits} />
}

const isValidUUID = (uuid: string): boolean => {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(uuid)
}

const uuidToBigInt = (uuid: string): bigint => {
// strip if it has dashes
const uuid_ = uuid.replace(/-/g, '')
return BigInt(`0x${uuid_}`)
}


export const BitIcon = (props: { bits: bigint }): JSX.Element => {
const bits = props.bits
if (!is128Bit(bits)) {
return (<span>Invalid 128bit value</span>)
}
const items = from128bitTo8bitArray(bits)
const width = 64
const height = 64
const numOfItems = items.length
const numOfRows = Math.ceil(Math.sqrt(numOfItems))
const numOfCols = Math.ceil(numOfItems / numOfRows)
const itemWidth = width / numOfCols
const itemHeight = height / numOfRows
const text = items.reduce((acc, item) => acc + item.toString(16), '')

return <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} xmlns="http://www.w3.org/2000/svg" aria-label={`BitIcon: ${text}`} role="img">
{Array.from(items).map((item: number, index: number) => {
const indexOfX = index % numOfCols // 0-7
const indexOfY = Math.floor(index / numOfCols) // 0-7
const x = indexOfX * itemWidth
const y = indexOfY * itemHeight
const color = valueToColor(item)
// biome-ignore lint/suspicious/noArrayIndexKey: SVG elements require a unique key
return <rect key={index} x={x} y={y} width={itemWidth} height={itemHeight} fill={color} />
})}
</svg>
}

/**
* Convert value to color
* @param value 0-7
* @returns color string
*/
const valueToColor = (value: number): string => {
return COLORS[value]
}

const is128Bit = (value: bigint): boolean => {
return value >= BigInt(0) && value <= BigInt('0xffffffffffffffffffffffffffffffff')
}

/**
* Convert 128bit value to int8 array
* @param value 128bit value
* @returns int8 array of 64 elements (0-7)
*/
export const from128bitTo8bitArray = (value: bigint): Uint8Array => {
let value_ = value
const bytes = new Uint8Array(64)
for (let i = 0; i < 64; i++) {
// take 2 bits from the value
const bit = Number(value_ & BigInt(0b11)) // 0-3
// shift 2 bits
value_ >>= BigInt(2)
bytes[i] = bit
}
return bytes
}
73 changes: 73 additions & 0 deletions packages/biticon/src/bitShuffle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const S_BOX: number[] = [ // AES S-Box
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
] as const;


const applySBox = (input: Uint8Array): Uint8Array => {
const output = new Uint8Array(input.length);
for (let i = 0; i < input.length; i++) {
output[i] = S_BOX[input[i]];
}
return output;
}

const xorShift = (data: Uint8Array): Uint8Array => {
const output = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
const prev = data[(i - 1 + data.length) % data.length];
const next = data[(i + 1) % data.length];
output[i] = data[i] ^ prev ^ next;
}
return output;
}

const applyMultipleTimes = (input: Uint8Array, times: number): Uint8Array => {
let output = input;
for (let i = 0; i < times; i++) {
output = xorShift(output);
output = applySBox(output);
output = xorShift(output);
}
return output;
}


export const applySBox128bitBigintTo128bitBigint = (input: bigint): bigint => {
const inputArray = bigint128bitToUint8Array(input)
const outputArray = applyMultipleTimes(inputArray, 16)
return uint8ArrayToBigint128bit(outputArray)
}

const bigint128bitToUint8Array = (value: bigint): Uint8Array => {
let value_ = value
const bytes = new Uint8Array(16)
for (let i = 0; i < 16; i++) {
const bit = Number(value_ & BigInt(0xff)) // 0-255
value_ >>= BigInt(8)
bytes[i] = bit
}
return bytes
}

const uint8ArrayToBigint128bit = (value: Uint8Array): bigint => {
let value_ = BigInt(0)
for (let i = 0; i < 16; i++) {
value_ |= BigInt(value[i]) << BigInt(i * 8)
}
return value_
}
8 changes: 8 additions & 0 deletions packages/biticon/src/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Based on the colormap viridis from the colormap package (CC0)
// https://github.com/BIDS/colormap/blob/master/colormaps.py
export const COLORS = [
'#440154',
'#30678d',
'#35b778',
'#fde724',
] as const
1 change: 1 addition & 0 deletions packages/biticon/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './base'
4 changes: 4 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"dev": "tsup --config ../../tsup.config.mjs --watch"
},
"devDependencies": {
"@kitsuyui/react-binary": "workspace:^",
"@kitsuyui/react-biticon": "workspace:^",
"@kitsuyui/react-clock": "workspace:^",
"@kitsuyui/react-dekamoji": "workspace:^",
"@kitsuyui/react-editablelabel": "workspace:^",
Expand All @@ -39,6 +41,8 @@
"react-use": "^17.5.0"
},
"peerDependencies": {
"@kitsuyui/react-binary": "workspace:latest",
"@kitsuyui/react-biticon": "workspace:latest",
"@kitsuyui/react-clock": "workspace:latest",
"@kitsuyui/react-dekamoji": "workspace:latest",
"@kitsuyui/react-editablelabel": "workspace:latest",
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"@kitsuyui/react-binary#build": {
"outputs": ["packages/binary/dist/**"]
},
"@kitsuyui/react-biticon#build": {
"outputs": ["packages/biticon/dist/**"]
},
"@kitsuyui/react-components#build": {
"dependsOn": [
"@kitsuyui/react-binary#build",
Expand All @@ -51,6 +54,7 @@
"@kitsuyui/react-textfield#build",
"@kitsuyui/react-measure#build",
"@kitsuyui/react-treemap#build",
"@kitsuyui/react-biticon#build",
"@kitsuyui/react-wavebox#build"
],
"outputs": ["packages/components/dist/**"]
Expand All @@ -72,6 +76,7 @@
"@kitsuyui/react-measure#build",
"@kitsuyui/react-treemap#build",
"@kitsuyui/react-wavebox#build",
"@kitsuyui/react-biticon#build",
"@kitsuyui/react-editablelabel#build",
"@kitsuyui/react-style-bulma#build"
],
Expand Down
Loading