Skip to content

Commit 66397b1

Browse files
committed
Rename files to lowercase
1 parent e5ddb49 commit 66397b1

6 files changed

Lines changed: 801 additions & 0 deletions

File tree

src/components/add.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useState, useEffect } from "react"
2+
3+
import Table, { Tables } from './table.tsx'
4+
import type { Radix } from '#/utils.ts'
5+
6+
const { isNaN } = Number
7+
8+
export default function Add({ radixes }: { radixes: Radix[] }) {
9+
return <Tables>{ radixes.map(radix => <AddTable radix={radix} key={radix.name}/>) }</Tables>
10+
}
11+
12+
function AddTable({ radix }: { radix: Radix }) {
13+
const [ props, setProps ] = useState(computeProps(radix))
14+
15+
useEffect(() => { setProps(computeProps(radix)) }, [ radix ])
16+
17+
return <Table radix={radix} {...props}/>
18+
}
19+
20+
function computeProps(radix: Radix) {
21+
let { low, high } = radix
22+
if (high + low > 0) low = 1
23+
if (low >= 0 && high < 3) high = 2
24+
const lowest = low >= 0 ? low : low + low
25+
const highest = high + high
26+
const arr = [ NaN, ...Array.from(Array(high - (low - 1)), (_, i) => i + low) ]
27+
const numbers = arr.map(row => arr.map(col => {
28+
if (isNaN(row) && isNaN(col)) return NaN
29+
return isNaN(row) || isNaN(col) ? isNaN(row) ? col : row : row + col
30+
}))
31+
32+
return { numbers, low: lowest, high: highest }
33+
}

src/components/convert.tsx

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { type ComponentProps, type InputEventHandler, type ClipboardEventHandler, useState, useEffectEvent, useEffect, useRef } from 'react'
2+
import { getErrorMessage } from 'react-error-boundary'
3+
4+
import type { UpdateValue } from '#/app.tsx'
5+
import { sanitizeInput } from "#/common.ts"
6+
import { getCharsForTooltip } from './table.tsx'
7+
import { type Radix, num2str, str2num, allowedCharaters, createRadix, } from '#/utils.ts'
8+
9+
const BIG_INT_0 = 0n
10+
const BIG_INT_1 = 1n
11+
12+
export default function Convert({ radixes, value, updateValue }: {
13+
radixes: Radix[],
14+
value: bigint,
15+
updateValue: UpdateValue
16+
}) {
17+
const plusButtonRef = useRef<HTMLButtonElement>(null)
18+
const deleteButtonRef = useRef<HTMLButtonElement>(null)
19+
const minusButtonRef = useRef<HTMLButtonElement>(null)
20+
21+
const keyDown = useEffectEvent((e: KeyboardEvent) => {
22+
switch (e.key) {
23+
case 'Backspace':
24+
case 'Delete':
25+
deleteButtonRef.current?.focus()
26+
updateValue(BIG_INT_0)
27+
break
28+
case '+':
29+
case '=':
30+
plusButtonRef.current?.focus()
31+
updateValue(value + BIG_INT_1)
32+
break
33+
case '-':
34+
case '_':
35+
minusButtonRef.current?.focus()
36+
updateValue(value - BIG_INT_1)
37+
break
38+
}
39+
})
40+
41+
useEffect(() => {
42+
document.addEventListener('keydown', keyDown)
43+
return () => { document.removeEventListener('keydown', keyDown) }
44+
}, [])
45+
46+
return <main className="flex flex-col text-[clamp(1.3rem,2.3vw,2.1rem)] mx-[clamp(0.5rem,1.5vw,2rem)]">
47+
<div className="flex relative lg:left-32 max-w-fit gap-1">
48+
<span className="tooltip tooltip-top" data-tip="Increment">
49+
<button className="btn btn-circle btn-sm md:btn-xs lg:btn-sm" ref={plusButtonRef} type="button" onClick={() => { updateValue(value + BIG_INT_1) }}>+</button>
50+
</span>
51+
<span className="tooltip tooltip-top" data-tip="Reset">
52+
<button className="btn btn-circle btn-sm md:btn-xs lg:btn-sm" ref={deleteButtonRef} type="button" onClick={() => { updateValue(BIG_INT_0) }}></button>
53+
</span>
54+
<span className="tooltip tooltip-top" data-tip="Decrement">
55+
<button className="btn btn-circle btn-sm md:btn-xs lg:btn-sm" ref={minusButtonRef} type="button" onClick={() => { updateValue(value - BIG_INT_1) }}>-</button>
56+
</span>
57+
</div>{ radixes.map((radix, index) =>
58+
<div key={radix.name}>
59+
<span className="hidden lg:inline-block text-center w-32">
60+
<span className="tooltip tooltip-right whitespace-pre before:content-[attr(data-tip)] before:max-w-200" data-tip={ getCharsForTooltip(radix) }>
61+
<span className="badge badge-neutral badge-outline badge-lg align-middle">{radix.name}</span>
62+
</span>
63+
</span>
64+
<span className="hidden md:inline-flex gap-1">
65+
<div className="tooltip tooltip-top" data-tip="Filling shift left">
66+
<button className="btn btn-circle btn-xs lg:btn-sm inline-block align-middle" type="button" onClick={() => { updateValue(filling_shl(value, radix), radix) }}></button>
67+
</div>
68+
<div className="tooltip tooltip-top" data-tip="Shift left">
69+
<button className="btn btn-circle btn-xs lg:btn-sm inline-block align-middle" disabled={ value === BIG_INT_0 || radix.system === 'bijective' || radix.system === 'sum'} type="button" onClick={() => { updateValue(shl(value, radix), radix) }}></button>
70+
</div>
71+
<div className="tooltip tooltip-top" data-tip="Shift right">
72+
<button className="btn btn-circle btn-xs lg:btn-sm inline-block align-middle" disabled={ value === BIG_INT_0 } type="button" onClick={() => { updateValue(shr(value, radix), radix) }}></button>
73+
</div>
74+
</span>
75+
<span> = </span>
76+
<NumberLine value={value} radix={radix} radixIndex={index} numRadixes={radixes.length} updateValue={updateValue}/>
77+
</div> )}
78+
</main>
79+
}
80+
81+
function NumberLine({ value, radix, radixIndex, numRadixes, updateValue }: ComponentProps<'div'> & {
82+
value: bigint,
83+
radix: Radix,
84+
radixIndex: number,
85+
numRadixes: number
86+
updateValue: UpdateValue
87+
}) {
88+
const [ strVal, setStrVal ] = useState(num2str(value, radix))
89+
const [ editing, setEditing ] = useState(false)
90+
const [ error, setError ] = useState<unknown>()
91+
const [ errorLevel, setErrorLevel ] = useState<'error' | 'warning'>('error')
92+
const ref = useRef<HTMLSpanElement>(null)
93+
94+
const updateError = (error: unknown, errorLvl: typeof errorLevel) => {
95+
setError(error)
96+
setErrorLevel(errorLvl)
97+
setTimeout(() => { setError(undefined) }, 10_000)
98+
}
99+
100+
const setCaretPosition = (position: number) => {
101+
setTimeout(() => { if (ref.current) getSelection()?.setPosition(ref.current.childNodes[0], position) }, 0)
102+
}
103+
104+
const handleInput: InputEventHandler<HTMLSpanElement> = (e) => {
105+
e.stopPropagation()
106+
107+
const s = e.currentTarget.textContent.toUpperCase()
108+
if (s === '') {
109+
setStrVal('')
110+
updateValue(BIG_INT_0)
111+
return
112+
}
113+
114+
let position = getCaretPosition()
115+
try {
116+
const n = str2num(s, radix)
117+
setStrVal(s)
118+
updateValue(n, radix)
119+
setError(undefined)
120+
} catch (error) {
121+
updateError(error, 'error')
122+
e.currentTarget.textContent = strVal
123+
position -= 1
124+
}
125+
setCaretPosition(position)
126+
}
127+
128+
const handlePaste: ClipboardEventHandler<HTMLSpanElement> = (e) => {
129+
e.preventDefault()
130+
131+
const [ input, rest ] = sanitizeInput(e.clipboardData.getData('text'), radix)
132+
if (rest) {
133+
updateError(`Non-Base characters "${rest}" has been filtered out. ${allowedCharaters(radix)}`, 'warning')
134+
}
135+
136+
const position = getCaretPosition()
137+
const range = getSelection()?.getRangeAt(0)
138+
139+
const newV = range?.startContainer === ref.current ? input : Array.from(strVal).toSpliced(position, range ? range.endOffset - range.startOffset : 0, input).join('')
140+
141+
try {
142+
updateValue(str2num(newV, radix), radix)
143+
setStrVal(newV)
144+
setCaretPosition(position + input.length)
145+
} catch (error) {
146+
updateError(error, 'error')
147+
}
148+
}
149+
150+
useEffect(() => { if (!editing) setStrVal(num2str(value, radix)) }, [ editing, value, radix ])
151+
152+
return <>
153+
<span className={`font-mono font-medium break-all outline-none${error ? ` tooltip tooltip-open tooltip-${errorLevel}` : ''}`} data-tip={getErrorMessage(error) ?? 'Unknown error'}
154+
role="textbox"
155+
tabIndex={0}
156+
contentEditable
157+
suppressContentEditableWarning
158+
spellCheck={false}
159+
onKeyDown={e => { if (e.key === 'Escape' || e.key === 'Enter') { e.currentTarget.blur() } else e.stopPropagation() }}
160+
onInput={handleInput}
161+
onPaste={handlePaste}
162+
onDoubleClick={() => { if (ref.current) getSelection()?.selectAllChildren(ref.current) }}
163+
onFocus={() => { setEditing(true) }}
164+
onBlur={() => { setEditing(false); setError(undefined); setStrVal(num2str(value, radix)) }}
165+
style={{ color: `hsl(${radixIndex / numRadixes * 300} 80% 40%)` }}
166+
ref={ref}
167+
>
168+
{strVal}
169+
</span>
170+
<sub className="lg:hidden align-middle text-[0.6rem]">{radix.name}</sub>
171+
<span className="text-[0.5em]">
172+
<span>
173+
<span> #{strVal.length} </span>
174+
</span> { getDigitSumArray(value, radix).map(([sum, system]) =>
175+
<span key={`${system}-${sum}`}>
176+
<span className="whitespace-nowrap"></span>
177+
<span>=</span>
178+
<span className="font-mono font-medium">{sum}</span>
179+
<sub className="text-nowrap">{system}</sub>
180+
</span> )}
181+
</span>
182+
</>
183+
}
184+
185+
function getDigitSumArray(number: bigint, radix: Radix) : [string, string][] {
186+
let num = num2str(number, radix)
187+
188+
let neg = false
189+
if (num.startsWith('-')) {
190+
neg = true
191+
num = num.slice(1)
192+
}
193+
194+
let n = Iterator.from(num).reduce((a, v) => a + str2num(v, radix), 0n)
195+
if (neg) n = -n
196+
197+
num = num2str(n, radix)
198+
199+
if (radix.system === 'standard' && radix.radix === 10n) {
200+
return (num.length === 1 || neg && num.length === 2) ? [[ num, radix.name ]] : [[ num, radix.name ], ...getDigitSumArray(n, radix)]
201+
}
202+
203+
return [[ num, radix.name ], ...getDigitSumArray(n, createRadix(10, 'standard'))]
204+
}
205+
206+
const getCaretPosition = () => getSelection()?.getRangeAt(0).startOffset ?? 0
207+
208+
function filling_shl(value: bigint, radix: Radix): bigint {
209+
return value ? value > 0 ? value * radix.radix + 1n : value * radix.radix - 1n : 1n
210+
}
211+
212+
function shl(value: bigint, radix: Radix): bigint {
213+
return value * radix.radix
214+
}
215+
216+
function shr(value: bigint, radix: Radix): bigint {
217+
return radix.system === 'sum' ? str2num(num2str(value, radix).slice(1), radix) : str2num(num2str(value, radix).slice(0, -1), radix)
218+
}

0 commit comments

Comments
 (0)