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';
|
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 lineColorState = atom<string>(COLORS[0]!);
|
||||||
|
|
||||||
export const drawModeState = atom<DrawMode>('line');
|
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 { 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';
|
import './grid-config.scss';
|
||||||
|
|
||||||
@ -51,6 +66,26 @@ const GridIcon = (
|
|||||||
</svg>
|
</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 ColorOption: FC<ColorOptionProps> = (props: ColorOptionProps) => {
|
||||||
const { color } = props;
|
const { color } = props;
|
||||||
const [lineColor, setLineColor] = useAtom(lineColorState);
|
const [lineColor, setLineColor] = useAtom(lineColorState);
|
||||||
@ -76,17 +111,41 @@ const ColorOption: FC<ColorOptionProps> = (props: ColorOptionProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ColorConfig: FC = () => (
|
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">
|
<div className="color-options">
|
||||||
{COLORS.map((color) => (
|
{COLORS.map((color) => (
|
||||||
<ColorOption key={color} color={color} />
|
<ColorOption key={color} color={color} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ModeConfig: FC = () => {
|
const ModeConfig: FC = () => {
|
||||||
const [drawMode, setDrawMode] = useAtom(drawModeState);
|
const [drawMode, setDrawMode] = useAtom(drawModeState);
|
||||||
|
|
||||||
|
useKeyPress(() => setDrawMode('trash'), 't');
|
||||||
|
|
||||||
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 = drawMode === 'grid' ? 'option active' : 'option';
|
const gridClassName = drawMode === 'grid' ? 'option active' : 'option';
|
||||||
@ -116,11 +175,92 @@ const ModeConfig: FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GridConfig: FC = () => (
|
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">
|
<div className="grid-config">
|
||||||
<ColorConfig />
|
<ColorConfig />
|
||||||
<ModeConfig />
|
<ModeConfig />
|
||||||
|
<StateConfig />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default GridConfig;
|
export default GridConfig;
|
||||||
|
@ -12,14 +12,23 @@ import {
|
|||||||
import './grid.scss';
|
import './grid.scss';
|
||||||
|
|
||||||
import * as lodash from 'lodash';
|
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';
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
interface Point {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Range {
|
interface Range {
|
||||||
x0: number;
|
x0: number;
|
||||||
y0: number;
|
y0: number;
|
||||||
@ -27,40 +36,6 @@ interface Range {
|
|||||||
y1: number;
|
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://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||||
// https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function
|
// https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function
|
||||||
function lineSegmentIntersection(l0: Range, l1: Range): boolean {
|
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 };
|
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 THICK_COLOR = '#313d49';
|
||||||
const THIN_COLOR = '#1e2226';
|
const THIN_COLOR = '#1e2226';
|
||||||
|
|
||||||
@ -200,12 +155,11 @@ const Grid: FC = () => {
|
|||||||
return lines_thin.concat(lines_thick);
|
return lines_thin.concat(lines_thick);
|
||||||
}, [range, GAP]);
|
}, [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 [userStartPoint, setUserStartPoint] = useAtom(userStartPointState);
|
||||||
const [userRedoActions, setUserRedoActions] = useState<Action[]>([]);
|
|
||||||
|
|
||||||
const [userStartPoint, setUserStartPoint] = useState<Point | null>(null);
|
|
||||||
const [userLineColor, setUserLineColor] = useAtom(lineColorState);
|
const [userLineColor, setUserLineColor] = useAtom(lineColorState);
|
||||||
const [userDrawMode, setUserDrawMode] = useAtom(drawModeState);
|
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(
|
const gridLineElements = useMemo(
|
||||||
() =>
|
() =>
|
||||||
gridLines.map((line, idx) => (
|
gridLines.map((line, idx) => (
|
||||||
|
Loading…
Reference in New Issue
Block a user