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 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 { PrimitiveAtom, useAtom, WritableAtom } from 'jotai';
import {
COLORS,
drawGridHeightState,
drawGridWidthState,
drawModeState,
lineColorState,
} from '../../atoms';
import { PrimitiveAtom, useAtom } from 'jotai';
import { COLORS, drawModeState, lineColorState } from '../../atoms';
import './grid-config.scss';
@ -46,16 +40,14 @@ const GridIcon = (
<svg
className="icon"
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 12 12"
viewBox="0 0 24 13"
>
<line x1="0" y1="0" x2="12" y2="0" stroke="#ffffff" strokeWidth="2" />
<line x1="0" y1="6" x2="12" y2="6" stroke="#ffffff" strokeWidth="2" />
<line x1="0" y1="12" x2="12" y2="12" stroke="#ffffff" strokeWidth="2" />
<line x1="0" y1="0" x2="0" y2="12" stroke="#ffffff" strokeWidth="2" />
<line x1="6" y1="0" x2="6" y2="12" stroke="#ffffff" strokeWidth="2" />
<line x1="12" y1="0" x2="12" y2="12" stroke="#ffffff" strokeWidth="2" />
<line x1="5" y1="0" x2="18" y2="0" stroke="#ffffff" strokeWidth="4" />
<line x1="5" y1="7" x2="18" y2="7" stroke="#ffffff" strokeWidth="2" />
<line x1="5" y1="13" x2="18" y2="13" stroke="#ffffff" strokeWidth="4" />
<line x1="5" y1="0" x2="5" y2="13" stroke="#ffffff" strokeWidth="2" />
<line x1="11" y1="0" x2="11" y2="13" stroke="#ffffff" strokeWidth="2" />
<line x1="17" y1="0" x2="17" y2="13" stroke="#ffffff" strokeWidth="2" />
</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 = () => (
<div className="color-options">
{COLORS.map((color) => (
@ -130,8 +89,7 @@ const ModeConfig: FC = () => {
const lineClassName = drawMode === 'line' ? 'option active' : 'option';
const trashClassName = drawMode === 'trash' ? 'option active' : 'option';
const gridClassName =
drawMode === 'grid' ? 'option active grid' : 'option grid';
const gridClassName = drawMode === 'grid' ? 'option active' : 'option';
return (
<>
<div className="tool-options">
@ -147,22 +105,11 @@ const ModeConfig: FC = () => {
>
{TrashIcon}
</div>
</div>
<div className="grid-options">
<div
className={gridClassName}
onClick={() => setDrawMode('grid')}
>
{GridIcon}
<NumberStateInput
className="grid-size width"
state={drawGridWidthState}
/>
<span className="x">x</span>
<NumberStateInput
className="grid-size height"
state={drawGridHeightState}
/>
</div>
</div>
</>

View File

@ -181,7 +181,14 @@ const Grid: FC = () => {
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)) {
setUserStartPoint(null);
@ -249,6 +256,12 @@ const Grid: FC = () => {
// ctrl+z/ctrl+y for undo/redo
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];
if (!action) return;
@ -264,7 +277,7 @@ const Grid: FC = () => {
}
setUserActions(userActions.slice(0, -1));
setUserRedoActions([...userRedoActions, action]);
}, [userLines, userActions, userRedoActions]);
}, [userLines, userActions, userRedoActions, userStartPoint]);
const onRedo = useCallback(() => {
const action = userRedoActions[userRedoActions.length - 1];
@ -341,6 +354,7 @@ const Grid: FC = () => {
strokeWidth="2"
/>
);
// TODO: grid mode
} else {
console.assert(userDrawMode === 'trash');
return (