make lots of stuff atoms and add stateconfig element
This commit is contained in:
parent
1cd12a6ac2
commit
7229c48a9a
47
src/atoms.ts
47
src/atoms.ts
@ -13,6 +13,53 @@ export const COLORS = [
|
||||
|
||||
type DrawMode = 'line' | 'trash' | 'grid';
|
||||
|
||||
export interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Line {
|
||||
x0: number;
|
||||
y0: number;
|
||||
x1: number;
|
||||
y1: number;
|
||||
stroke: string;
|
||||
}
|
||||
|
||||
export interface AddLineAction {
|
||||
line: Line;
|
||||
}
|
||||
|
||||
export interface AddLinesAction {
|
||||
addLines: Line[];
|
||||
}
|
||||
|
||||
export interface DeleteLinesAction {
|
||||
deleteLines: Line[];
|
||||
}
|
||||
|
||||
export type Action = AddLineAction | AddLinesAction | DeleteLinesAction;
|
||||
|
||||
export function isAddLineAction(action: Action): action is AddLineAction {
|
||||
return 'line' in action;
|
||||
}
|
||||
|
||||
export function isAddLinesAction(action: Action): action is AddLinesAction {
|
||||
return 'addLines' in action;
|
||||
}
|
||||
|
||||
export function isDeleteLinesAction(
|
||||
action: Action
|
||||
): action is DeleteLinesAction {
|
||||
return 'deleteLines' in action;
|
||||
}
|
||||
|
||||
export const lineColorState = atom<string>(COLORS[0]!);
|
||||
|
||||
export const drawModeState = atom<DrawMode>('line');
|
||||
|
||||
export const userLinesState = atom<Line[]>([]);
|
||||
export const userActionsState = atom<Action[]>([]);
|
||||
export const userRedoActionsState = atom<Action[]>([]);
|
||||
|
||||
export const userStartPointState = atom<Point | null>(null);
|
||||
|
@ -1,6 +1,21 @@
|
||||
import { ChangeEvent, FC, useCallback } from 'react';
|
||||
import { ChangeEvent, FC, useCallback, useEffect } from 'react';
|
||||
import * as lodash from 'lodash';
|
||||
import { PrimitiveAtom, useAtom } from 'jotai';
|
||||
import { COLORS, drawModeState, lineColorState } from '../../atoms';
|
||||
import {
|
||||
Action,
|
||||
COLORS,
|
||||
drawModeState,
|
||||
isAddLineAction,
|
||||
isAddLinesAction,
|
||||
isDeleteLinesAction,
|
||||
Line,
|
||||
lineColorState,
|
||||
Point,
|
||||
userActionsState,
|
||||
userLinesState,
|
||||
userRedoActionsState,
|
||||
userStartPointState,
|
||||
} from '../../atoms';
|
||||
|
||||
import './grid-config.scss';
|
||||
|
||||
@ -51,6 +66,26 @@ const GridIcon = (
|
||||
</svg>
|
||||
);
|
||||
|
||||
function useKeyPress(
|
||||
callback: (event: KeyboardEvent) => void,
|
||||
key: string,
|
||||
ctrlKey: boolean = false
|
||||
) {
|
||||
useEffect(() => {
|
||||
const listener = (eventRaw: Event) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const event = eventRaw as any as KeyboardEvent;
|
||||
if (event.key === key && event.ctrlKey === ctrlKey) {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', listener);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', listener);
|
||||
};
|
||||
}, [callback, key, ctrlKey]);
|
||||
}
|
||||
|
||||
const ColorOption: FC<ColorOptionProps> = (props: ColorOptionProps) => {
|
||||
const { color } = props;
|
||||
const [lineColor, setLineColor] = useAtom(lineColorState);
|
||||
@ -76,17 +111,41 @@ const ColorOption: FC<ColorOptionProps> = (props: ColorOptionProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ColorConfig: FC = () => (
|
||||
<div className="color-options">
|
||||
{COLORS.map((color) => (
|
||||
<ColorOption key={color} color={color} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
const ColorConfig: FC = () => {
|
||||
const [userLineColor, setUserLineColor] = useAtom(lineColorState);
|
||||
const [userDrawMode, setUserDrawMode] = useAtom(drawModeState);
|
||||
|
||||
const setUserColorBind = useCallback(
|
||||
(color: string) => {
|
||||
setUserLineColor(color);
|
||||
setUserDrawMode('line');
|
||||
},
|
||||
[setUserLineColor, setUserDrawMode]
|
||||
);
|
||||
|
||||
useKeyPress(() => setUserColorBind(COLORS[0]!), '1');
|
||||
useKeyPress(() => setUserColorBind(COLORS[1]!), '2');
|
||||
useKeyPress(() => setUserColorBind(COLORS[2]!), '3');
|
||||
useKeyPress(() => setUserColorBind(COLORS[3]!), '4');
|
||||
useKeyPress(() => setUserColorBind(COLORS[4]!), '5');
|
||||
useKeyPress(() => setUserColorBind(COLORS[5]!), '6');
|
||||
useKeyPress(() => setUserColorBind(COLORS[6]!), '7');
|
||||
useKeyPress(() => setUserColorBind(COLORS[7]!), '8');
|
||||
|
||||
return (
|
||||
<div className="color-options">
|
||||
{COLORS.map((color) => (
|
||||
<ColorOption key={color} color={color} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ModeConfig: FC = () => {
|
||||
const [drawMode, setDrawMode] = useAtom(drawModeState);
|
||||
|
||||
useKeyPress(() => setDrawMode('trash'), 't');
|
||||
|
||||
const lineClassName = drawMode === 'line' ? 'option active' : 'option';
|
||||
const trashClassName = drawMode === 'trash' ? 'option active' : 'option';
|
||||
const gridClassName = drawMode === 'grid' ? 'option active' : 'option';
|
||||
@ -116,11 +175,92 @@ const ModeConfig: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const GridConfig: FC = () => (
|
||||
<div className="grid-config">
|
||||
<ColorConfig />
|
||||
<ModeConfig />
|
||||
</div>
|
||||
);
|
||||
const StateConfig: FC = () => {
|
||||
const [userLines, setUserLines] = useAtom(userLinesState);
|
||||
const [userActions, setUserActions] = useAtom(userActionsState);
|
||||
const [userRedoActions, setUserRedoActions] = useAtom(userRedoActionsState);
|
||||
|
||||
const [userStartPoint, setUserStartPoint] = useAtom(userStartPointState);
|
||||
|
||||
// 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;
|
||||
|
||||
if (isAddLineAction(action)) {
|
||||
// remove action.line
|
||||
setUserLines(
|
||||
userLines.filter((userLine) => userLine !== action.line)
|
||||
);
|
||||
} else if (isAddLinesAction(action)) {
|
||||
// remove all of action.addLines
|
||||
setUserLines(
|
||||
userLines.filter(
|
||||
(userLine) =>
|
||||
!action.addLines.find(
|
||||
(actionLine) => actionLine === userLine
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (isDeleteLinesAction(action)) {
|
||||
// add back all of action.deleteLines
|
||||
// NOTE: this does not preserve layering
|
||||
setUserLines([...userLines, ...action.deleteLines]);
|
||||
} else {
|
||||
console.error('invalid action');
|
||||
}
|
||||
setUserActions(userActions.slice(0, -1));
|
||||
setUserRedoActions([...userRedoActions, action]);
|
||||
}, [userLines, userActions, userRedoActions, userStartPoint]);
|
||||
|
||||
const onRedo = useCallback(() => {
|
||||
const action = userRedoActions[userRedoActions.length - 1];
|
||||
if (!action) return;
|
||||
|
||||
if (isAddLineAction(action)) {
|
||||
// add back action.line
|
||||
setUserLines([...userLines, action.line]);
|
||||
} else if (isAddLinesAction(action)) {
|
||||
// add back all of action.addLines
|
||||
// NOTE: this does not preserve layering
|
||||
setUserLines([...userLines, ...action.addLines]);
|
||||
} else if (isDeleteLinesAction(action)) {
|
||||
// remove all of action.delteLines
|
||||
setUserLines(
|
||||
userLines.filter(
|
||||
(userLine) =>
|
||||
!action.deleteLines.find(
|
||||
(actionLine) => userLine === actionLine
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
throw new Error('invalid action');
|
||||
}
|
||||
setUserRedoActions(userRedoActions.slice(0, -1));
|
||||
setUserActions([...userActions, action]);
|
||||
}, [userLines, userActions, userRedoActions]);
|
||||
|
||||
useKeyPress(onUndo, 'z', true);
|
||||
useKeyPress(onRedo, 'y', true);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const GridConfig: FC = () => {
|
||||
return (
|
||||
<div className="grid-config">
|
||||
<ColorConfig />
|
||||
<ModeConfig />
|
||||
<StateConfig />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GridConfig;
|
||||
|
@ -12,14 +12,23 @@ import {
|
||||
import './grid.scss';
|
||||
|
||||
import * as lodash from 'lodash';
|
||||
import { COLORS, drawModeState, lineColorState } from '../../atoms';
|
||||
import {
|
||||
COLORS,
|
||||
drawModeState,
|
||||
lineColorState,
|
||||
Line,
|
||||
Point,
|
||||
Action,
|
||||
AddLineAction,
|
||||
AddLinesAction,
|
||||
DeleteLinesAction,
|
||||
userLinesState,
|
||||
userActionsState,
|
||||
userRedoActionsState,
|
||||
userStartPointState,
|
||||
} from '../../atoms';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface Range {
|
||||
x0: number;
|
||||
y0: number;
|
||||
@ -27,40 +36,6 @@ interface Range {
|
||||
y1: number;
|
||||
}
|
||||
|
||||
interface Line {
|
||||
x0: number;
|
||||
y0: number;
|
||||
x1: number;
|
||||
y1: number;
|
||||
stroke: string;
|
||||
}
|
||||
|
||||
interface AddLineAction {
|
||||
line: Line;
|
||||
}
|
||||
|
||||
interface AddLinesAction {
|
||||
addLines: Line[];
|
||||
}
|
||||
|
||||
interface DeleteLinesAction {
|
||||
deleteLines: Line[];
|
||||
}
|
||||
|
||||
type Action = AddLineAction | AddLinesAction | DeleteLinesAction;
|
||||
|
||||
function isAddLineAction(action: Action): action is AddLineAction {
|
||||
return 'line' in action;
|
||||
}
|
||||
|
||||
function isAddLinesAction(action: Action): action is AddLinesAction {
|
||||
return 'addLines' in action;
|
||||
}
|
||||
|
||||
function isDeleteLinesAction(action: Action): action is DeleteLinesAction {
|
||||
return 'deleteLines' in action;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
// https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function
|
||||
function lineSegmentIntersection(l0: Range, l1: Range): boolean {
|
||||
@ -141,26 +116,6 @@ function useMouseInRange(range: Range): Point {
|
||||
return { x: mouse.x + range.x0, y: mouse.y + range.y0 };
|
||||
}
|
||||
|
||||
function useKeyPress(
|
||||
callback: (event: KeyboardEvent) => void,
|
||||
key: string,
|
||||
ctrlKey: boolean = false
|
||||
) {
|
||||
useEffect(() => {
|
||||
const listener = (eventRaw: Event) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const event = eventRaw as any as KeyboardEvent;
|
||||
if (event.key === key && event.ctrlKey === ctrlKey) {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', listener);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', listener);
|
||||
};
|
||||
}, [callback, key, ctrlKey]);
|
||||
}
|
||||
|
||||
const THICK_COLOR = '#313d49';
|
||||
const THIN_COLOR = '#1e2226';
|
||||
|
||||
@ -200,12 +155,11 @@ const Grid: FC = () => {
|
||||
return lines_thin.concat(lines_thick);
|
||||
}, [range, GAP]);
|
||||
|
||||
const [userLines, setUserLines] = useState<Line[]>([]);
|
||||
const [userLines, setUserLines] = useAtom(userLinesState);
|
||||
const [userActions, setUserActions] = useAtom(userActionsState);
|
||||
const [userRedoActions, setUserRedoActions] = useAtom(userRedoActionsState);
|
||||
|
||||
const [userActions, setUserActions] = useState<Action[]>([]);
|
||||
const [userRedoActions, setUserRedoActions] = useState<Action[]>([]);
|
||||
|
||||
const [userStartPoint, setUserStartPoint] = useState<Point | null>(null);
|
||||
const [userStartPoint, setUserStartPoint] = useAtom(userStartPointState);
|
||||
const [userLineColor, setUserLineColor] = useAtom(lineColorState);
|
||||
const [userDrawMode, setUserDrawMode] = useAtom(drawModeState);
|
||||
|
||||
@ -309,93 +263,6 @@ 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;
|
||||
|
||||
if (isAddLineAction(action)) {
|
||||
// remove action.line
|
||||
setUserLines(
|
||||
userLines.filter((userLine) => userLine !== action.line)
|
||||
);
|
||||
} else if (isAddLinesAction(action)) {
|
||||
// remove all of action.addLines
|
||||
setUserLines(
|
||||
userLines.filter(
|
||||
(userLine) =>
|
||||
!action.addLines.find(
|
||||
(actionLine) => actionLine === userLine
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (isDeleteLinesAction(action)) {
|
||||
// add back all of action.deleteLines
|
||||
// NOTE: this does not preserve layering
|
||||
setUserLines([...userLines, ...action.deleteLines]);
|
||||
} else {
|
||||
console.error('invalid action');
|
||||
}
|
||||
setUserActions(userActions.slice(0, -1));
|
||||
setUserRedoActions([...userRedoActions, action]);
|
||||
}, [userLines, userActions, userRedoActions, userStartPoint]);
|
||||
|
||||
const onRedo = useCallback(() => {
|
||||
const action = userRedoActions[userRedoActions.length - 1];
|
||||
if (!action) return;
|
||||
|
||||
if (isAddLineAction(action)) {
|
||||
// add back action.line
|
||||
setUserLines([...userLines, action.line]);
|
||||
} else if (isAddLinesAction(action)) {
|
||||
// add back all of action.addLines
|
||||
// NOTE: this does not preserve layering
|
||||
setUserLines([...userLines, ...action.addLines]);
|
||||
} else if (isDeleteLinesAction(action)) {
|
||||
// remove all of action.delteLines
|
||||
setUserLines(
|
||||
userLines.filter(
|
||||
(userLine) =>
|
||||
!action.deleteLines.find(
|
||||
(actionLine) => userLine === actionLine
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
throw new Error('invalid action');
|
||||
}
|
||||
setUserRedoActions(userRedoActions.slice(0, -1));
|
||||
setUserActions([...userActions, action]);
|
||||
}, [userLines, userActions, userRedoActions]);
|
||||
|
||||
useKeyPress(onUndo, 'z', true);
|
||||
useKeyPress(onRedo, 'y', true);
|
||||
|
||||
const setUserColorBind = useCallback(
|
||||
(color: string) => {
|
||||
setUserLineColor(color);
|
||||
setUserDrawMode('line');
|
||||
},
|
||||
[setUserLineColor, setUserDrawMode]
|
||||
);
|
||||
|
||||
useKeyPress(() => setUserColorBind(COLORS[0]!), '1');
|
||||
useKeyPress(() => setUserColorBind(COLORS[1]!), '2');
|
||||
useKeyPress(() => setUserColorBind(COLORS[2]!), '3');
|
||||
useKeyPress(() => setUserColorBind(COLORS[3]!), '4');
|
||||
useKeyPress(() => setUserColorBind(COLORS[4]!), '5');
|
||||
useKeyPress(() => setUserColorBind(COLORS[5]!), '6');
|
||||
useKeyPress(() => setUserColorBind(COLORS[6]!), '7');
|
||||
useKeyPress(() => setUserColorBind(COLORS[7]!), '8');
|
||||
|
||||
useKeyPress(() => setUserDrawMode('trash'), 't');
|
||||
|
||||
const gridLineElements = useMemo(
|
||||
() =>
|
||||
gridLines.map((line, idx) => (
|
||||
|
Loading…
Reference in New Issue
Block a user