Skip to content

Commit

Permalink
Add useCurrentInput hook
Browse files Browse the repository at this point in the history
  • Loading branch information
printfn committed Dec 6, 2024
1 parent 5b5eccb commit 9687d3d
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 42 deletions.
59 changes: 17 additions & 42 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import {
type FormEvent,
type KeyboardEvent,
type ReactNode,
useCallback,
useEffect,
useRef,
useState,
useTransition,
} from 'react';
import { type KeyboardEvent, type ReactNode, useCallback, useEffect, useRef, useState, useTransition } from 'react';
import { ThreeDotsScale } from 'react-svg-spinners';
import { fend } from './lib/fend';
import { useHistory } from './hooks/useHistory';
import { useCurrentInput } from './hooks/useCurrentInput';

const examples = `
> 5'10" to cm
Expand Down Expand Up @@ -51,11 +42,9 @@ function NewTabLink({ children, href }: { children: ReactNode; href: string }) {
}

export default function App({ widget = false }: { widget?: boolean }) {
const [currentInput, setCurrentInput] = useState('');
const [output, setOutput] = useState<ReactNode>(widget ? <></> : exampleContent);
const { history, addToHistory } = useHistory();
const { currentInput, addToHistory, onInput, upArrow, downArrow } = useCurrentInput();
const [variables, setVariables] = useState('');
const [navigation, setNavigation] = useState(0);
const [hint, setHint] = useState('');
useEffect(() => {
void (async () => {
Expand All @@ -77,10 +66,7 @@ export default function App({ widget = false }: { widget?: boolean }) {
inputText.current?.focus();
}
}, []);
const update = useCallback((e: FormEvent<HTMLTextAreaElement>) => {
setCurrentInput(e.currentTarget.value);
setNavigation(0);
}, []);

const [isPending, startTransition] = useTransition();
const onKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
Expand All @@ -92,19 +78,14 @@ export default function App({ widget = false }: { widget?: boolean }) {
setOutput(null);
return;
}
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
if (navigation > 0) {
event.preventDefault();
if (event.key === 'ArrowUp') {
setNavigation(n => Math.min(n + 1, history.length));
} else {
setNavigation(n => Math.max(n - 1, 0));
setCurrentInput('');
}
} else if (currentInput.trim().length === 0 && event.key === 'ArrowUp' && history.length > 0) {
event.preventDefault();
setNavigation(1);
}
if (event.key === 'ArrowUp') {
event.preventDefault();
upArrow();
return;
}
if (event.key === 'ArrowDown') {
event.preventDefault();
downArrow();
return;
}

Expand All @@ -115,20 +96,19 @@ export default function App({ widget = false }: { widget?: boolean }) {
}
event.preventDefault();
if (currentInput.trim() === 'clear') {
setCurrentInput('');
onInput('');
setOutput(null);
return;
}

startTransition(async () => {
const request = <p>{`> ${currentInput}`}</p>;
addToHistory(currentInput);
setNavigation(0);
const fendResult = await fend(currentInput, 1000000000, variables);
if (!fendResult.ok && fendResult.message === 'cancelled') {
return;
}
setCurrentInput('');
onInput('');
console.log(fendResult);
const result = <p>{fendResult.ok ? fendResult.result : fendResult.message}</p>;
if (fendResult.ok && fendResult.variables.length > 0) {
Expand All @@ -144,19 +124,14 @@ export default function App({ widget = false }: { widget?: boolean }) {
inputHint.current?.scrollIntoView();
});
},
[currentInput, addToHistory, variables, history.length, navigation],
[currentInput, addToHistory, variables, onInput, downArrow, upArrow],
);
useEffect(() => {
document.addEventListener('click', focus);
return () => {
document.removeEventListener('click', focus);
};
}, [focus]);
useEffect(() => {
if (navigation > 0) {
setCurrentInput(history[history.length - navigation]);
}
}, [navigation, history]);
return (
<main>
{!widget && (
Expand All @@ -177,13 +152,13 @@ export default function App({ widget = false }: { widget?: boolean }) {
rows={currentInput.split('\n').length}
ref={inputText}
value={currentInput}
onInput={update}
onInput={onInput}
onKeyDown={onKeyDown}
autoFocus
/>
</div>
<p id="input-hint" ref={inputHint}>
{hint || (isPending ? <ThreeDotsScale /> : null)}
{hint || (isPending ? <ThreeDotsScale /> : <>&nbsp;</>)}
</p>
</div>
</main>
Expand Down
65 changes: 65 additions & 0 deletions web/src/hooks/useCurrentInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { FormEvent, useCallback, useState } from 'react';
import { useHistory } from './useHistory';

export function useCurrentInput() {
const { history, addToHistory: saveToHistory } = useHistory();
const [currentInput, setCurrentInput] = useState('');
const [navigation, setNavigation] = useState(0);

const navigate = useCallback(
(direction: 'up' | 'down' | 'bottom') => {
setNavigation(n => {
let newValue: number;
switch (direction) {
case 'up':
newValue = Math.min(n + 1, history.length);
break;
case 'down':
newValue = Math.max(n - 1, 0);
break;
case 'bottom':
newValue = 0;
break;
}
if (newValue > 0) {
setCurrentInput(history[history.length - newValue]);
}
if (newValue === 0 && (direction === 'up' || direction === 'down')) {
setCurrentInput('');
}
return newValue;
});
},
[history],
);

const onInput = useCallback(
(e: string | FormEvent<HTMLTextAreaElement>) => {
navigate('bottom');
setCurrentInput(typeof e === 'string' ? e : e.currentTarget.value);
},
[navigate],
);

const upArrow = useCallback(() => {
if (currentInput.trim().length !== 0 && navigation === 0) {
// todo we should allow navigating history if input has been typed
return;
}
navigate('up');
}, [currentInput, navigate, navigation]);

const downArrow = useCallback(() => {
navigate('down');
}, [navigate]);

const addToHistory = useCallback(
(newEntry: string) => {
saveToHistory(newEntry);
setNavigation(0);
},
[saveToHistory],
);

return { currentInput, addToHistory, onInput, downArrow, upArrow };
}

0 comments on commit 9687d3d

Please sign in to comment.