Skip to content

Commit

Permalink
Small improvements and refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
kuchta committed Nov 28, 2023
1 parent 44cf034 commit 9083ed3
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 154 deletions.
72 changes: 42 additions & 30 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@ import Show from './Show'
import Add from './Add'
import Multiply from './Multiply'
import Convert from './Convert'
import { Radix, createRadixes, createRadix, getCharsLS, getRadixesLS, setRadixesLS, str2arr, num2str, str2num, allowedCharaters, sanitizeInput } from '../utils'
import {
Radix,
createRadixes,
createRadix,
getCharsLS,
getRadixesLS,
setRadixesLS,
str2arr,
num2str,
str2num,
allowedCharaters,
sanitizeInput
} from '../utils'


export default function App() {
const { radixes, enabledRadixes, setRadixes, value, setValue, error } = useStore()
const [ error, setError ] = useState<string>()
const updateError = (error?: string) => { setError(error); setTimeout(() => setError(undefined), 10000) }
const { radixes, enabledRadixes, updateRadixes, value, updateValue } = useStore(updateError)
const { pathname, search } = useLocation()

// console.log('App: ', { value })
Expand All @@ -21,7 +35,7 @@ export default function App() {
<span>{ error }</span>
</div>
</div> }
<Header radixes={radixes} setRadixes={setRadixes}/>
<Header radixes={radixes} updateRadixes={updateRadixes}/>
<nav className="tabs tabs-bordered justify-center mb-4">
<Link className={`tab ${pathname === '/' ? 'tab-active' : ''}`} to={`/${search}`}>Show</Link>
<Link className={`tab ${pathname.includes('add') ? 'tab-active' : ''}`} to={`add${search}`}>Add</Link>
Expand All @@ -32,56 +46,54 @@ export default function App() {
<Route path="/" element={<Show radixes={enabledRadixes}/>}/>
<Route path="add" element={<Add radixes={enabledRadixes}/>}/>
<Route path="multiply" element={<Multiply radixes={enabledRadixes}/>}/>
<Route path="convert" element={<Convert radixes={enabledRadixes} value={value} setValue={setValue}/>}/>
<Route path="convert" element={<Convert radixes={enabledRadixes} value={value} updateValue={updateValue}/>}/>
</Routes>
</>
}

function useStore() {
const [ radixes, _setRadixes ] = useState(getRadixesLS() ?? createRadixes(str2arr(getCharsLS())))
function useStore(updateError: (error?: string) => void) {
const [ radixes, setRadixes ] = useState(getRadixesLS() ?? createRadixes(str2arr(getCharsLS())))
const [ enabledRadixes, setEnabledRadixes ] = useState(radixes.filter(v => v.enabled))
const [ searchParams, setSearchParams ] = useSearchParams()
const [ _value, _setValue ] = useState(0n)
const [ _radix, _setRadix ] = useState(createRadix(10))
const [ error, setError ] = useState<string>()
const [ _value, setValue ] = useState(0n)
const [ _radix, setRadix ] = useState(createRadix(10))

useEffect(() => {
try {
if (searchParams.has('r')) {
const searchRadixes = searchParams.getAll('r')
radixes.forEach(r => r.enabled = searchRadixes.includes(r.name))
setRadixes(radixes)
updateRadixes(radixes)
setEnabledRadixes(radixes.filter(v => v.enabled))
}
let rx = _radix
if (searchParams.has('radix')) {
rx = radixes.find(r => r.name === searchParams.get('radix'))!
_setRadix(rx)
let radix = _radix
const sRadix = searchParams.get('radix')
if (sRadix) {
const r = radixes.find(r => r.name === sRadix)
if (r == undefined) throw new Error(`Unknown radix "${r}" in the URL`)
setRadix(radix = r)
}
if (searchParams.has('value')) {
const [ value, rest ] = sanitizeInput(searchParams.get('value')!, rx)
_setValue(str2num(value, rx))
if (rest) {
setError(`Non-Base characters "${rest}" has been filtered out. ${allowedCharaters(rx)}`)
setTimeout(() => setError(undefined), 10000)
}
const sValue = searchParams.get('value')
if (sValue) {
const [ value, rest ] = sanitizeInput(sValue, radix)
if (rest) throw new Error(`Non-Base characters "${rest}" has been filtered out. ${allowedCharaters(radix)}`)
setValue(str2num(value, radix))
}
} catch (error) {
setError((error as Error).message)
setTimeout(() => setError(undefined), 10000)
updateError((error as Error).message)
}
}, [])

const setRadixes = (radixes: Radix[]) => {
const updateRadixes = (radixes: Radix[]) => {
setRadixesLS(radixes)
_setRadixes(radixes)
setRadixes(radixes)
const enabledRadixes = radixes.filter(v => v.enabled)
setEnabledRadixes(enabledRadixes)
searchParams.delete('r')
setSearchParams([ ...searchParams, ...enabledRadixes.map(r => ['r', r.name]) ] as [string, string][])
}

const setValue = (value: bigint | ((value: bigint) => bigint), radix?: Radix) => {
const updateValue = (value: bigint | ((value: bigint) => bigint), radix?: Radix) => {
value = (typeof value === 'function') ? value(_value) : value
if (value === 0n) {
searchParams.delete('radix')
Expand All @@ -91,17 +103,17 @@ function useStore() {
try {
const r = radix ?? _radix ?? createRadix(10, 'standard')
if (radix) {
_setRadix(radix)
setRadix(radix)
searchParams.set('radix', r.name)
}
searchParams.set('value', num2str(value, r))
setSearchParams(searchParams)
} catch (error) {
setError((error as Error).message)
updateError((error as Error).message)
}
}
_setValue(value)
setValue(value)
}

return { radixes, enabledRadixes, setRadixes, value: _value, setValue, error }
return { radixes, enabledRadixes, updateRadixes, value: _value, updateValue }
}
51 changes: 22 additions & 29 deletions src/components/Convert.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useState, memo, useEffect, useRef } from 'react'
import React, { useState, useEffect, useRef } from 'react'

import { Radix, num2str, str2num, filling_shl, shl, shr, areRadixesEqual, allowedCharaters, sanitizeInput } from '../utils'
import { Radix, num2str, str2num, filling_shl, shl, shr, allowedCharaters, sanitizeInput } from '../utils'


export default function Convert({ radixes, value, setValue }: {
export default function Convert({ radixes, value, updateValue }: {
radixes: Radix[],
value: bigint,
setValue: (value: bigint | ((value: bigint) => bigint), radix?: Radix) => void
updateValue: (value: bigint | ((value: bigint) => bigint), radix?: Radix) => void
}) {
const plusButtonRef = useRef<HTMLButtonElement>(null)
const deleteButtonRef = useRef<HTMLButtonElement>(null)
Expand All @@ -19,17 +19,17 @@ export default function Convert({ radixes, value, setValue }: {
case 'Backspace':
case 'Delete':
deleteButtonRef.current?.focus()
setValue(0n)
updateValue(0n)
break
case '+':
case '=':
plusButtonRef.current?.focus()
setValue(v => v + 1n)
updateValue(v => v + 1n)
break
case '-':
case '_':
minusButtonRef.current?.focus()
setValue(v => v - 1n)
updateValue(v => v - 1n)
break
}
}
Expand All @@ -44,56 +44,49 @@ export default function Convert({ radixes, value, setValue }: {
return <main>
<div className="flex flex-col gap-1 items-start relative w-full text-[3vh] mx-0 my-[3vh] pl-[3vw]">
<div className="flex flex-row gap-1">
<button className="btn btn-circle btn-sm text-lg" ref={plusButtonRef} onClick={() => setValue(value + 1n)}>+</button>
<button className="btn btn-circle btn-sm text-2xl" ref={deleteButtonRef} onClick={() => setValue(0n)}></button>
<button className="btn btn-circle btn-sm text-lg" ref={minusButtonRef} onClick={() => setValue(value - 1n)}>-</button>
<button className="btn btn-circle btn-sm text-lg" ref={plusButtonRef} onClick={() => updateValue(value + 1n)}>+</button>
<button className="btn btn-circle btn-sm text-2xl" ref={deleteButtonRef} onClick={() => updateValue(0n)}></button>
<button className="btn btn-circle btn-sm text-lg" ref={minusButtonRef} onClick={() => updateValue(value - 1n)}>-</button>
</div>
{ radixes.map((radix, index) =>
<div key={radix.name}>
<span className="flex flex-row gap-1 items-center float-left leading-8" key={radix.name}>
<button className="btn btn-sm btn-circle text-lg" onClick={() => setValue(filling_shl(value, radix), radix)}></button>
<button className="btn btn-sm btn-circle text-lg" disabled={ value === 0n || radix.system === "bijective" } onClick={() => setValue(shl(value, radix), radix)}></button>
<button className="btn btn-sm btn-circle text-lg" disabled={ value === 0n } onClick={() => setValue(shr(value, radix), radix)}></button>
<button className="btn btn-sm btn-circle text-lg" onClick={() => updateValue(filling_shl(value, radix), radix)}></button>
<button className="btn btn-sm btn-circle text-lg" disabled={ value === 0n || radix.system === "bijective" } onClick={() => updateValue(shl(value, radix), radix)}></button>
<button className="btn btn-sm btn-circle text-lg" disabled={ value === 0n } onClick={() => updateValue(shr(value, radix), radix)}></button>
<span className="text-[1.2em]">=</span>
</span>
<NumberLine
value={value}
radix={radix}
radixIndex={index}
numRadixes={radixes.length}
setValue={setValue} />
updateValue={updateValue} />
</div>
)}
</div>
</main>
}

function NumberLine({ value, radix, radixIndex, numRadixes, setValue }: {
function NumberLine({ value, radix, radixIndex, numRadixes, updateValue }: {
value: bigint,
radix: Radix,
radixIndex: number,
numRadixes: number
setValue: (value: bigint | ((value: bigint) => bigint), radix?: Radix) => void
updateValue: (value: bigint | ((value: bigint) => bigint), radix?: Radix) => void
}) {
const [ v, setV ] = useState(num2str(value, radix))
const ref = useRef<HTMLSpanElement>(null)
const [ editing, setEditing ] = useState(false)
const [ error, setError ] = useState<string>()
const [ errorLevel, setErrorLevel ] = useState<'error' | 'warning'>('error')
const ref = useRef<HTMLSpanElement>(null)

useEffect(() => {
if (!editing) setV(num2str(value, radix))
}, [ value, radix ])
useEffect(() => { if (!editing) setV(num2str(value, radix)) }, [ value, radix ])

const getCaretPosition = () => window.getSelection()?.getRangeAt(0).startOffset ?? 0

const setCaretPosition = (position: number) => {
setTimeout(() => {
// console.log('position:', position)
if (ref.current) {
window.getSelection()?.setPosition(ref.current.childNodes[0], position)
}
}, 0)
setTimeout(() => { if (ref.current) window.getSelection()?.setPosition(ref.current.childNodes[0], position) }, 0)
}

const handleInput = (e: React.FormEvent<HTMLSpanElement>) => {
Expand All @@ -104,15 +97,15 @@ function NumberLine({ value, radix, radixIndex, numRadixes, setValue }: {
const s = e.currentTarget.innerText //.trim().toUpperCase()
if (s === '') {
setV('')
setValue(0n)
updateValue(0n)
return
}

let position = getCaretPosition()
try {
const n = str2num(s, radix)
setV(s)
setValue(n, radix)
updateValue(n, radix)
setError(undefined)
} catch (error) {
setError((error as Error).message)
Expand All @@ -137,7 +130,7 @@ function NumberLine({ value, radix, radixIndex, numRadixes, setValue }: {
const newV = [].toSpliced.call(v, position, selectionRange, input).join('')
const n = str2num(newV, radix)
setV(newV)
setValue(n, radix)
updateValue(n, radix)
position += input.length
if (rest) {
setError(`Non-Base characters "${rest}" has been filtered out. ${allowedCharaters(radix)}`)
Expand Down
Loading

0 comments on commit 9083ed3

Please sign in to comment.