actually have a more usable way in mind

This commit is contained in:
Michael Peters 2024-06-27 14:17:11 -07:00
parent 89ecd65999
commit 584eb559bd
4 changed files with 58 additions and 68 deletions

View File

@ -0,0 +1,32 @@
interface StateInputProps<T> {
state: PrimitiveAtom<T>;
className?: string;
}
const NumberStateInput: FC<StateInputProps<number | null>> = (
props: StateInputProps<number | null>
) => {
const { state, className } = props;
const [number, setNumber] = useAtom(state);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const re = /^\d*$/;
const v = e.target.value;
if (v === '') {
setNumber(null);
} else if (re.test(v)) {
setNumber(parseInt(v));
}
},
[setNumber]
);
return (
<input
className={className}
value={number ?? ''}
onChange={handleChange}
/>
);
};

View File

@ -16,6 +16,3 @@ type DrawMode = 'line' | 'trash' | 'grid';
export const lineColorState = atom<string>(COLORS[0]!); export const lineColorState = atom<string>(COLORS[0]!);
export const drawModeState = atom<DrawMode>('line'); export const drawModeState = atom<DrawMode>('line');
export const drawGridWidthState = atom<number | null>(4);
export const drawGridHeightState = atom<number | null>(4);

View File

@ -1,12 +1,6 @@
import { ChangeEvent, FC, useCallback } from 'react'; import { ChangeEvent, FC, useCallback } from 'react';
import { PrimitiveAtom, useAtom, WritableAtom } from 'jotai'; import { PrimitiveAtom, useAtom } from 'jotai';
import { import { COLORS, drawModeState, lineColorState } from '../../atoms';
COLORS,
drawGridHeightState,
drawGridWidthState,
drawModeState,
lineColorState,
} from '../../atoms';
import './grid-config.scss'; import './grid-config.scss';
@ -46,16 +40,14 @@ const GridIcon = (
<svg <svg
className="icon" className="icon"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="12" viewBox="0 0 24 13"
height="12"
viewBox="0 0 12 12"
> >
<line x1="0" y1="0" x2="12" y2="0" stroke="#ffffff" strokeWidth="2" /> <line x1="5" y1="0" x2="18" y2="0" stroke="#ffffff" strokeWidth="4" />
<line x1="0" y1="6" x2="12" y2="6" stroke="#ffffff" strokeWidth="2" /> <line x1="5" y1="7" x2="18" y2="7" stroke="#ffffff" strokeWidth="2" />
<line x1="0" y1="12" x2="12" y2="12" stroke="#ffffff" strokeWidth="2" /> <line x1="5" y1="13" x2="18" y2="13" stroke="#ffffff" strokeWidth="4" />
<line x1="0" y1="0" x2="0" y2="12" stroke="#ffffff" strokeWidth="2" /> <line x1="5" y1="0" x2="5" y2="13" stroke="#ffffff" strokeWidth="2" />
<line x1="6" y1="0" x2="6" y2="12" stroke="#ffffff" strokeWidth="2" /> <line x1="11" y1="0" x2="11" y2="13" stroke="#ffffff" strokeWidth="2" />
<line x1="12" y1="0" x2="12" y2="12" stroke="#ffffff" strokeWidth="2" /> <line x1="17" y1="0" x2="17" y2="13" stroke="#ffffff" strokeWidth="2" />
</svg> </svg>
); );
@ -84,39 +76,6 @@ const ColorOption: FC<ColorOptionProps> = (props: ColorOptionProps) => {
); );
}; };
interface StateInputProps<T> {
state: PrimitiveAtom<T>;
className?: string;
}
const NumberStateInput: FC<StateInputProps<number | null>> = (
props: StateInputProps<number | null>
) => {
const { state, className } = props;
const [number, setNumber] = useAtom(state);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const re = /^\d*$/;
const v = e.target.value;
if (v === '') {
setNumber(null);
} else if (re.test(v)) {
setNumber(parseInt(v));
}
},
[setNumber]
);
return (
<input
className={className}
value={number ?? ''}
onChange={handleChange}
/>
);
};
const ColorConfig: FC = () => ( const ColorConfig: FC = () => (
<div className="color-options"> <div className="color-options">
{COLORS.map((color) => ( {COLORS.map((color) => (
@ -130,8 +89,7 @@ const ModeConfig: FC = () => {
const lineClassName = drawMode === 'line' ? 'option active' : 'option'; const lineClassName = drawMode === 'line' ? 'option active' : 'option';
const trashClassName = drawMode === 'trash' ? 'option active' : 'option'; const trashClassName = drawMode === 'trash' ? 'option active' : 'option';
const gridClassName = const gridClassName = drawMode === 'grid' ? 'option active' : 'option';
drawMode === 'grid' ? 'option active grid' : 'option grid';
return ( return (
<> <>
<div className="tool-options"> <div className="tool-options">
@ -147,22 +105,11 @@ const ModeConfig: FC = () => {
> >
{TrashIcon} {TrashIcon}
</div> </div>
</div>
<div className="grid-options">
<div <div
className={gridClassName} className={gridClassName}
onClick={() => setDrawMode('grid')} onClick={() => setDrawMode('grid')}
> >
{GridIcon} {GridIcon}
<NumberStateInput
className="grid-size width"
state={drawGridWidthState}
/>
<span className="x">x</span>
<NumberStateInput
className="grid-size height"
state={drawGridHeightState}
/>
</div> </div>
</div> </div>
</> </>

View File

@ -181,7 +181,14 @@ const Grid: FC = () => {
return; return;
} }
const point = snapToGrid(mousePoint, GAP); let point;
if (userDrawMode === 'grid') {
// snap to the thick grid
point = snapToGrid(mousePoint, GAP * 6);
} else {
// snap to the thin grid for line / trash
point = snapToGrid(mousePoint, GAP);
}
if (lodash.isEqual(userStartPoint, point)) { if (lodash.isEqual(userStartPoint, point)) {
setUserStartPoint(null); setUserStartPoint(null);
@ -249,6 +256,12 @@ const Grid: FC = () => {
// ctrl+z/ctrl+y for undo/redo // ctrl+z/ctrl+y for undo/redo
const onUndo = useCallback(() => { const onUndo = useCallback(() => {
if (!lodash.isNull(userStartPoint)) {
// if the user has a start point, just cancel the pending line
setUserStartPoint(null);
return;
}
const action = userActions[userActions.length - 1]; const action = userActions[userActions.length - 1];
if (!action) return; if (!action) return;
@ -264,7 +277,7 @@ const Grid: FC = () => {
} }
setUserActions(userActions.slice(0, -1)); setUserActions(userActions.slice(0, -1));
setUserRedoActions([...userRedoActions, action]); setUserRedoActions([...userRedoActions, action]);
}, [userLines, userActions, userRedoActions]); }, [userLines, userActions, userRedoActions, userStartPoint]);
const onRedo = useCallback(() => { const onRedo = useCallback(() => {
const action = userRedoActions[userRedoActions.length - 1]; const action = userRedoActions[userRedoActions.length - 1];
@ -341,6 +354,7 @@ const Grid: FC = () => {
strokeWidth="2" strokeWidth="2"
/> />
); );
// TODO: grid mode
} else { } else {
console.assert(userDrawMode === 'trash'); console.assert(userDrawMode === 'trash');
return ( return (