Added color reducer. Performed state management refactor to prevent circular behavior.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import type { Dispatch } from "react";
|
||||
|
||||
import * as colorlib from "colorlib";
|
||||
|
||||
import type { SetterValueOrCallback } from "@/types";
|
||||
|
||||
export interface ColorState {
|
||||
color: colorlib.Color;
|
||||
}
|
||||
|
||||
export type ColorAction =
|
||||
| { type: "SET_COLOR"; payload: colorlib.Color }
|
||||
| { type: "SET_RGB"; payload: colorlib.RGB }
|
||||
| { type: "SET_HSV"; payload: colorlib.HSV }
|
||||
| { type: "SET_HCL"; payload: colorlib.HCL }
|
||||
| { type: "SET_HEX"; payload: colorlib.Hex }
|
||||
| {
|
||||
type: "SET_VALUE";
|
||||
component: colorlib.Component;
|
||||
payload: SetterValueOrCallback<number>;
|
||||
};
|
||||
|
||||
export function colorReducer(
|
||||
state: ColorState,
|
||||
action: ColorAction,
|
||||
): ColorState {
|
||||
let comp;
|
||||
|
||||
switch (action.type) {
|
||||
case "SET_COLOR":
|
||||
return { ...state, color: action.payload };
|
||||
|
||||
case "SET_RGB":
|
||||
let rgb = action.payload;
|
||||
return { ...state, color: colorlib.Color.from_rgb(rgb.r, rgb.g, rgb.b) };
|
||||
|
||||
case "SET_HSV":
|
||||
let hsv = action.payload;
|
||||
return { ...state, color: colorlib.Color.from_hsv(hsv.h, hsv.s, hsv.v) };
|
||||
|
||||
case "SET_HCL":
|
||||
let hcl = action.payload;
|
||||
return { ...state, color: colorlib.Color.from_hcl(hcl.h, hcl.c, hcl.l) };
|
||||
|
||||
case "SET_HEX":
|
||||
let hex = action.payload;
|
||||
return { ...state, color: colorlib.Color.from_hex(hex.to_code()) };
|
||||
|
||||
case "SET_VALUE":
|
||||
comp = action.component;
|
||||
let valOrFn = action.payload;
|
||||
|
||||
if (typeof valOrFn === "function") {
|
||||
let prev = state.color.get(comp);
|
||||
return { ...state, color: state.color.update(comp, valOrFn(prev)) };
|
||||
} else {
|
||||
return { ...state, color: state.color.update(comp, valOrFn) };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
type Setter = (valOrCallback: SetterValueOrCallback<number>) => void;
|
||||
|
||||
export interface CommonColorActions {
|
||||
setColor: (color: colorlib.Color) => void;
|
||||
}
|
||||
|
||||
export interface RGBColorActions {
|
||||
setRGB: (rgb: colorlib.RGB) => void;
|
||||
setR: Setter;
|
||||
setG: Setter;
|
||||
setB: Setter;
|
||||
}
|
||||
|
||||
export interface HSVColorActions {
|
||||
setHSV: (hsv: colorlib.HSV) => void;
|
||||
setH: Setter;
|
||||
setS: Setter;
|
||||
setV: Setter;
|
||||
}
|
||||
|
||||
export interface HCLColorActions {
|
||||
setHCL: (hcl: colorlib.HCL) => void;
|
||||
setH: Setter;
|
||||
setC: Setter;
|
||||
setL: Setter;
|
||||
}
|
||||
|
||||
export interface HexColorActions {
|
||||
setHex: (hex: colorlib.Hex) => void;
|
||||
}
|
||||
|
||||
export interface ColorActions {
|
||||
common: CommonColorActions;
|
||||
rgb: RGBColorActions;
|
||||
hsv: HSVColorActions;
|
||||
hcl: HCLColorActions;
|
||||
hex: HexColorActions;
|
||||
}
|
||||
|
||||
export function createColorActions(
|
||||
dispatch: Dispatch<ColorAction>,
|
||||
): ColorActions {
|
||||
const Comp = colorlib.Component;
|
||||
const setValue = (
|
||||
comp: colorlib.Component,
|
||||
payload: SetterValueOrCallback<number>,
|
||||
) => dispatch({ type: "SET_VALUE", component: comp, payload });
|
||||
|
||||
return {
|
||||
common: {
|
||||
setColor: (payload) => dispatch({ type: "SET_COLOR", payload }),
|
||||
},
|
||||
|
||||
rgb: {
|
||||
setRGB: (rgb) => dispatch({ type: "SET_RGB", payload: rgb }),
|
||||
setR: (val) => setValue(Comp.RGB_R, val),
|
||||
setG: (val) => setValue(Comp.RGB_G, val),
|
||||
setB: (val) => setValue(Comp.RGB_B, val),
|
||||
},
|
||||
|
||||
hsv: {
|
||||
setHSV: (hsv) => dispatch({ type: "SET_HSV", payload: hsv }),
|
||||
setH: (val) => setValue(Comp.HSV_H, val),
|
||||
setS: (val) => setValue(Comp.HSV_S, val),
|
||||
setV: (val) => setValue(Comp.HSV_V, val),
|
||||
},
|
||||
|
||||
hcl: {
|
||||
setHCL: (hcl) => dispatch({ type: "SET_HCL", payload: hcl }),
|
||||
setH: (val) => setValue(Comp.HCL_H, val),
|
||||
setC: (val) => setValue(Comp.HCL_C, val),
|
||||
setL: (val) => setValue(Comp.HCL_L, val),
|
||||
},
|
||||
|
||||
hex: {
|
||||
setHex: (hex) => dispatch({ type: "SET_HEX", payload: hex }),
|
||||
},
|
||||
};
|
||||
}
|
||||
+47
-17
@@ -1,12 +1,13 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import type { CartesianSpace } from "@/types";
|
||||
import type { CartesianSpace, Range, Setter } from "@/types";
|
||||
import {
|
||||
extractEventCoordinates,
|
||||
isLeftMouseButton,
|
||||
isTouchEvent,
|
||||
minmax,
|
||||
positionToValue,
|
||||
valueToPosition,
|
||||
} from "@/util";
|
||||
|
||||
if (typeof TouchEvent === "undefined") {
|
||||
@@ -19,37 +20,57 @@ export function useCrosshair({
|
||||
dimensions,
|
||||
setXPosition,
|
||||
setYPosition,
|
||||
xValue,
|
||||
yValue,
|
||||
setXValue,
|
||||
setYValue,
|
||||
xValueRange,
|
||||
yValueRange,
|
||||
}: {
|
||||
origin: CartesianSpace;
|
||||
dimensions: CartesianSpace;
|
||||
setXPosition: Dispatch<SetStateAction<number>>;
|
||||
setYPosition: Dispatch<SetStateAction<number>>;
|
||||
setXPosition: Setter<number>;
|
||||
setYPosition: Setter<number>;
|
||||
xValue: number;
|
||||
yValue: number;
|
||||
setXValue: Setter<number>;
|
||||
setYValue: Setter<number>;
|
||||
xValueRange: Range;
|
||||
yValueRange: Range;
|
||||
}) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const crosshairRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const originRef = useRef(origin);
|
||||
const dimensionsRef = useRef(dimensions);
|
||||
const setXValueRef = useRef(setXValue);
|
||||
const setYValueRef = useRef(setYValue);
|
||||
const xValueRangeRef = useRef(xValueRange);
|
||||
const yValueRangeRef = useRef(yValueRange);
|
||||
|
||||
useEffect(() => {
|
||||
originRef.current = origin;
|
||||
dimensionsRef.current = dimensions;
|
||||
}, [origin, dimensions]);
|
||||
setXValueRef.current = setXValue;
|
||||
setYValueRef.current = setYValue;
|
||||
xValueRangeRef.current = xValueRange;
|
||||
yValueRangeRef.current = yValueRange;
|
||||
}, [origin, dimensions, setXValue, setYValue, xValueRange, yValueRange]);
|
||||
|
||||
const calculatePositions = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const orig = originRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
const calculatePositions = useCallback((event: MouseEvent | TouchEvent) => {
|
||||
const orig = originRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
const { clientX, clientY } = extractEventCoordinates(event);
|
||||
const { clientX, clientY } = extractEventCoordinates(event);
|
||||
|
||||
const xPos = minmax(clientX - orig.x, 0, dims.x - 1);
|
||||
const yPos = minmax(clientY - orig.y, 0, dims.y - 1);
|
||||
setXPosition(xPos);
|
||||
setYPosition(yPos);
|
||||
},
|
||||
[setXPosition, setYPosition],
|
||||
);
|
||||
const xPos = minmax(clientX - orig.x, 0, dims.x - 1);
|
||||
const yPos = minmax(clientY - orig.y, 0, dims.y - 1);
|
||||
const newXValue = positionToValue(xPos, dims.x - 1, xValueRangeRef.current);
|
||||
const newYValue = positionToValue(yPos, dims.y - 1, yValueRangeRef.current);
|
||||
|
||||
setXValueRef.current(newXValue);
|
||||
setYValueRef.current(newYValue);
|
||||
}, []);
|
||||
|
||||
const handleMove = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
@@ -101,6 +122,15 @@ export function useCrosshair({
|
||||
[calculatePositions, handleMove, handleEnd],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const dims = dimensionsRef.current;
|
||||
const newXPos = valueToPosition(xValue, dims.x - 1, xValueRangeRef.current);
|
||||
const newYPos = valueToPosition(yValue, dims.y - 1, yValueRangeRef.current);
|
||||
|
||||
if (newXPos === newXPos) setXPosition(newXPos);
|
||||
if (newYPos === newYPos) setYPosition(newYPos);
|
||||
}, [xValue, yValue, setXPosition, setYPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = crosshairRef.current;
|
||||
if (currentRef) {
|
||||
|
||||
+7
-9
@@ -35,7 +35,7 @@ export function useScroll<T extends HTMLElement>({
|
||||
onScrollDown: ScrollHandler;
|
||||
deltaYMultiplier?: number;
|
||||
}) {
|
||||
const [_, setScrollLength] = useState(0);
|
||||
const scrollLength = useRef(0);
|
||||
|
||||
const onScrollUpRef = useRef(onScrollUp);
|
||||
const onScrollDownRef = useRef(onScrollDown);
|
||||
@@ -49,16 +49,14 @@ export function useScroll<T extends HTMLElement>({
|
||||
|
||||
const handleWheelEvent = useCallback((event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
console.log("Handling wheel event.");
|
||||
|
||||
setScrollLength((prev) =>
|
||||
handleScroll(
|
||||
prev,
|
||||
event.deltaY * deltaYMultiplierRef.current,
|
||||
onScrollDownRef.current,
|
||||
onScrollUpRef.current,
|
||||
),
|
||||
const newScrollLength = handleScroll(
|
||||
scrollLength.current,
|
||||
event.deltaY * deltaYMultiplierRef.current,
|
||||
onScrollDownRef.current,
|
||||
onScrollUpRef.current,
|
||||
);
|
||||
scrollLength.current = newScrollLength;
|
||||
}, []);
|
||||
|
||||
const addScrollListener = useCallback(() => {
|
||||
|
||||
+86
-39
@@ -1,12 +1,15 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import type { CartesianSpace } from "@/types";
|
||||
import type { CartesianSpace, Range, Setter } from "@/types";
|
||||
import { Direction } from "@/types";
|
||||
import {
|
||||
chooseValueByDirection,
|
||||
extractEventCoordinates,
|
||||
isLeftMouseButton,
|
||||
isTouchEvent,
|
||||
minmax,
|
||||
positionToValue,
|
||||
valueToPosition,
|
||||
} from "@/util";
|
||||
|
||||
import { useScroll } from "./scroll";
|
||||
@@ -16,19 +19,6 @@ if (typeof TouchEvent === "undefined") {
|
||||
window.TouchEvent = window.MouseEvent;
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
HORIZONTAL = "horizontal",
|
||||
VERTICAL = "vertical",
|
||||
}
|
||||
|
||||
function chooseValueByDirection(
|
||||
direction: Direction,
|
||||
xValue: number,
|
||||
yValue: number,
|
||||
) {
|
||||
return direction === Direction.HORIZONTAL ? xValue : yValue;
|
||||
}
|
||||
|
||||
function extractEventCoordinateByDirection(
|
||||
event: MouseEvent | TouchEvent,
|
||||
direction: Direction,
|
||||
@@ -41,43 +31,74 @@ export function useSlider({
|
||||
direction,
|
||||
origin,
|
||||
dimensions,
|
||||
setPosition,
|
||||
valueRange,
|
||||
value,
|
||||
setValue,
|
||||
}: {
|
||||
direction: Direction;
|
||||
origin: CartesianSpace;
|
||||
dimensions: CartesianSpace;
|
||||
setPosition: Dispatch<SetStateAction<number>>;
|
||||
valueRange: Range;
|
||||
value: number;
|
||||
setValue: Setter<number>;
|
||||
}) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Slider UI refs
|
||||
const directionRef = useRef(direction);
|
||||
const originRef = useRef(origin);
|
||||
const dimensionsRef = useRef(dimensions);
|
||||
|
||||
// Slider value refs
|
||||
const setValueRef = useRef(setValue);
|
||||
const valueRangeRef = useRef(valueRange);
|
||||
const maxPosition = useRef(0);
|
||||
|
||||
// Internal position management
|
||||
const [position, setPosition] = useState(0);
|
||||
const positionRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
directionRef.current = direction;
|
||||
originRef.current = origin;
|
||||
dimensionsRef.current = dimensions;
|
||||
}, [direction, origin, dimensions]);
|
||||
maxPosition.current = chooseValueByDirection(
|
||||
direction,
|
||||
dimensions.x,
|
||||
dimensions.y,
|
||||
);
|
||||
valueRangeRef.current = valueRange;
|
||||
}, [direction, origin, dimensions, valueRangeRef]);
|
||||
|
||||
useEffect(() => {
|
||||
setValueRef.current = setValue;
|
||||
}, [setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
positionRef.current = position;
|
||||
}, [position]);
|
||||
|
||||
// Setup drag handlers
|
||||
const calculatePosition = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const dir = directionRef.current;
|
||||
const orig = originRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
const calculatePosition = useCallback((event: MouseEvent | TouchEvent) => {
|
||||
const dir = directionRef.current;
|
||||
const orig = originRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
const clientCoord = extractEventCoordinateByDirection(event, dir);
|
||||
const positionValue = minmax(
|
||||
clientCoord - chooseValueByDirection(dir, orig.x, orig.y),
|
||||
0,
|
||||
chooseValueByDirection(dir, dims.x, dims.y),
|
||||
);
|
||||
setPosition(positionValue);
|
||||
},
|
||||
[setPosition],
|
||||
);
|
||||
const clientCoord = extractEventCoordinateByDirection(event, dir);
|
||||
const newPosition = minmax(
|
||||
clientCoord - chooseValueByDirection(dir, orig.x, orig.y),
|
||||
0,
|
||||
chooseValueByDirection(dir, dims.x, dims.y),
|
||||
);
|
||||
const newValue = positionToValue(
|
||||
newPosition,
|
||||
maxPosition.current,
|
||||
valueRangeRef.current,
|
||||
);
|
||||
|
||||
setValueRef.current(newValue);
|
||||
}, []);
|
||||
|
||||
const handleMove = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
@@ -120,20 +141,37 @@ export function useSlider({
|
||||
const dims = dimensionsRef.current;
|
||||
const inc = chooseValueByDirection(dir, 1, -1);
|
||||
|
||||
setPosition((prev: number) =>
|
||||
minmax(prev + inc, 0, chooseValueByDirection(dir, dims.x, dims.y)),
|
||||
const newPosition = minmax(
|
||||
positionRef.current + inc,
|
||||
0,
|
||||
chooseValueByDirection(dir, dims.x, dims.y),
|
||||
);
|
||||
}, [setPosition]);
|
||||
const newValue = positionToValue(
|
||||
newPosition,
|
||||
maxPosition.current,
|
||||
valueRangeRef.current,
|
||||
);
|
||||
setValueRef.current(newValue);
|
||||
}, []);
|
||||
|
||||
const handleScrollDown = useCallback(() => {
|
||||
const dir = directionRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
const inc = chooseValueByDirection(dir, -1, 1);
|
||||
|
||||
setPosition((prev: number) =>
|
||||
minmax(prev + inc, 0, chooseValueByDirection(dir, dims.x, dims.y)),
|
||||
const newPosition = minmax(
|
||||
positionRef.current + inc,
|
||||
0,
|
||||
chooseValueByDirection(dir, dims.x, dims.y),
|
||||
);
|
||||
}, [setPosition]);
|
||||
const newValue = positionToValue(
|
||||
newPosition,
|
||||
maxPosition.current,
|
||||
valueRangeRef.current,
|
||||
);
|
||||
|
||||
setValueRef.current(newValue);
|
||||
}, []);
|
||||
|
||||
const { addScrollListener, removeScrollListener } = useScroll({
|
||||
targetRef: sliderRef,
|
||||
@@ -141,6 +179,15 @@ export function useSlider({
|
||||
onScrollDown: handleScrollDown,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const newPosition = valueToPosition(
|
||||
value,
|
||||
maxPosition.current,
|
||||
valueRangeRef.current,
|
||||
);
|
||||
setPosition(newPosition);
|
||||
}, [value, setPosition]);
|
||||
|
||||
// Set up entry listeners
|
||||
useEffect(() => {
|
||||
const currentRef = sliderRef.current;
|
||||
|
||||
@@ -0,0 +1,572 @@
|
||||
import * as colorlib from "colorlib";
|
||||
import { beforeEach, describe, expect, test } from "vitest";
|
||||
|
||||
import { colorReducer, createColorActions } from "../color";
|
||||
import type { ColorAction, ColorActions, ColorState } from "../color";
|
||||
|
||||
const mockUseReducer = <T extends object, U>(
|
||||
reducer: (state: T, action: U) => T,
|
||||
initialArg: T,
|
||||
): [T, (value: U) => void] => {
|
||||
let currentState = initialArg;
|
||||
|
||||
const state = new Proxy({} as T, {
|
||||
get: (_, prop) => currentState[prop as keyof T],
|
||||
});
|
||||
|
||||
const dispatch = (value: U) => {
|
||||
const nextState = reducer(currentState, value);
|
||||
currentState = nextState;
|
||||
};
|
||||
return [state, dispatch];
|
||||
};
|
||||
|
||||
const expectRGB = (value: colorlib.RGB, expected: colorlib.RGB) => {
|
||||
expect(value.r).toBe(expected.r);
|
||||
expect(value.g).toBe(expected.g);
|
||||
expect(value.b).toBe(expected.b);
|
||||
};
|
||||
|
||||
const expectHSV = (value: colorlib.HSV, expected: colorlib.HSV) => {
|
||||
expect(value.h).toBe(expected.h);
|
||||
expect(value.s).toBe(expected.s);
|
||||
expect(value.v).toBe(expected.v);
|
||||
};
|
||||
|
||||
const expectHCL = (value: colorlib.HCL, expected: colorlib.HCL) => {
|
||||
expect(value.h).toBe(expected.h);
|
||||
expect(value.c).toBe(expected.c);
|
||||
expect(value.l).toBe(expected.l);
|
||||
};
|
||||
|
||||
describe("color reducer", () => {
|
||||
const initialState = { color: colorlib.Color.from_hex("000") };
|
||||
|
||||
describe("set by color", () => {
|
||||
test("set color", () => {
|
||||
const nextColor = colorlib.Color.from_hex("F00");
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_COLOR",
|
||||
payload: nextColor,
|
||||
});
|
||||
expect(nextState.color.hex.to_code()).toBe(nextColor.hex.to_code());
|
||||
});
|
||||
});
|
||||
|
||||
describe("set by color space", () => {
|
||||
test("set rgb", () => {
|
||||
const nextColor = colorlib.RGB.new(1, 2, 3);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_RGB",
|
||||
payload: nextColor,
|
||||
});
|
||||
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("set hsv", () => {
|
||||
const nextColor = colorlib.HSV.new(1, 2, 3);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_HSV",
|
||||
payload: nextColor,
|
||||
});
|
||||
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("set hcl", () => {
|
||||
const nextColor = colorlib.HCL.new(1, 2, 3);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_HCL",
|
||||
payload: nextColor,
|
||||
});
|
||||
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("set hex", () => {
|
||||
const nextColor = colorlib.Hex.new(1, 2, 3);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_HEX",
|
||||
payload: nextColor,
|
||||
});
|
||||
|
||||
expect(nextState.color.hex.to_code()).toBe(nextColor.to_code());
|
||||
});
|
||||
});
|
||||
|
||||
describe("set by component", () => {
|
||||
describe("rgb", () => {
|
||||
test("set rgb r", () => {
|
||||
const nextColor = colorlib.RGB.new(100, 0, 0);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_R,
|
||||
payload: nextColor.r,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("set rgb g", () => {
|
||||
const nextColor = colorlib.RGB.new(0, 100, 0);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_G,
|
||||
payload: nextColor.g,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("set rgb b", () => {
|
||||
const nextColor = colorlib.RGB.new(0, 0, 100);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_B,
|
||||
payload: nextColor.b,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hsv", () => {
|
||||
test("set hsv h", () => {
|
||||
const nextColor = colorlib.HSV.new(100, 0, 0);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_H,
|
||||
payload: nextColor.h,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("set hsv s", () => {
|
||||
const nextColor = colorlib.HSV.new(0, 0.5, 0);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_S,
|
||||
payload: nextColor.s,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("set hsv v", () => {
|
||||
const nextColor = colorlib.HSV.new(0, 0, 0.5);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_V,
|
||||
payload: nextColor.v,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hcl", () => {
|
||||
test("set hcl h", () => {
|
||||
const nextColor = colorlib.HCL.new(100, 0, 0);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_H,
|
||||
payload: nextColor.h,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("set hcl c", () => {
|
||||
const nextColor = colorlib.HCL.new(0, 0.5, 0);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_C,
|
||||
payload: nextColor.c,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("set hcl l", () => {
|
||||
const nextColor = colorlib.HCL.new(0, 0, 0.5);
|
||||
const nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_L,
|
||||
payload: nextColor.l,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("adjust by component", () => {
|
||||
describe("rgb", () => {
|
||||
test("adjust rgb r", () => {
|
||||
let nextColor = colorlib.RGB.new(100, 0, 0);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_R,
|
||||
payload: (prev) => prev + 100,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
|
||||
nextColor = colorlib.RGB.new(50, 0, 0);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_R,
|
||||
payload: (prev) => prev - 50,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("adjust rgb g", () => {
|
||||
let nextColor = colorlib.RGB.new(0, 100, 0);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_G,
|
||||
payload: (prev) => prev + 100,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
|
||||
nextColor = colorlib.RGB.new(0, 50, 0);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_G,
|
||||
payload: (prev) => prev - 50,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("adjust rgb b", () => {
|
||||
let nextColor = colorlib.RGB.new(0, 0, 100);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_B,
|
||||
payload: (prev) => prev + 100,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
|
||||
nextColor = colorlib.RGB.new(0, 0, 50);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.RGB_B,
|
||||
payload: (prev) => prev - 50,
|
||||
});
|
||||
expectRGB(nextState.color.rgb, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hsv", () => {
|
||||
test("adjust hsv h", () => {
|
||||
let nextColor = colorlib.HSV.new(100, 0, 0);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_H,
|
||||
payload: (prev) => prev + 100,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
|
||||
nextColor = colorlib.HSV.new(50, 0, 0);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_H,
|
||||
payload: (prev) => prev - 50,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hsv s", () => {
|
||||
let nextColor = colorlib.HSV.new(0, 1, 0);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_S,
|
||||
payload: (prev) => prev + 1,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
|
||||
nextColor = colorlib.HSV.new(0, 0.5, 0);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_S,
|
||||
payload: (prev) => prev - 0.5,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hsv v", () => {
|
||||
let nextColor = colorlib.HSV.new(0, 0, 1);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_V,
|
||||
payload: (prev) => prev + 1,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
|
||||
nextColor = colorlib.HSV.new(0, 0, 0.5);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HSV_V,
|
||||
payload: (prev) => prev - 0.5,
|
||||
});
|
||||
expectHSV(nextState.color.hsv, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hcl", () => {
|
||||
test("adjust hcl h", () => {
|
||||
let nextColor = colorlib.HCL.new(100, 0, 0);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_H,
|
||||
payload: (prev) => prev + 100,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
|
||||
nextColor = colorlib.HCL.new(50, 0, 0);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_H,
|
||||
payload: (prev) => prev - 50,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hcl c", () => {
|
||||
let nextColor = colorlib.HCL.new(0, 1, 0);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_C,
|
||||
payload: (prev) => prev + 1,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
|
||||
nextColor = colorlib.HCL.new(0, 0.5, 0);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_C,
|
||||
payload: (prev) => prev - 0.5,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hcl l", () => {
|
||||
let nextColor = colorlib.HCL.new(0, 0, 1);
|
||||
let nextState = colorReducer(initialState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_L,
|
||||
payload: (prev) => prev + 1,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
|
||||
nextColor = colorlib.HCL.new(0, 0, 0.5);
|
||||
nextState = colorReducer(nextState, {
|
||||
type: "SET_VALUE",
|
||||
component: colorlib.Component.HCL_L,
|
||||
payload: (prev) => prev - 0.5,
|
||||
});
|
||||
expectHCL(nextState.color.hcl, nextColor);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("color actions", () => {
|
||||
const initialState = { color: colorlib.Color.from_hex("000") };
|
||||
|
||||
let state: ColorState;
|
||||
let dispatch: (value: ColorAction) => void;
|
||||
let actions: ColorActions;
|
||||
|
||||
beforeEach(() => {
|
||||
[state, dispatch] = mockUseReducer(colorReducer, initialState);
|
||||
actions = createColorActions(dispatch);
|
||||
});
|
||||
|
||||
describe("set by color", () => {
|
||||
test("set color", () => {
|
||||
const nextColor = colorlib.Color.from_hex("FF0000");
|
||||
actions.common.setColor(nextColor);
|
||||
expect(state.color.hex.to_code()).toBe(nextColor.hex.to_code());
|
||||
});
|
||||
});
|
||||
|
||||
describe("set by color space", () => {
|
||||
test("set rgb", () => {
|
||||
const nextColor = colorlib.RGB.new(1, 2, 3);
|
||||
actions.rgb.setRGB(nextColor);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("set hsv", () => {
|
||||
const nextColor = colorlib.HSV.new(1, 2, 3);
|
||||
actions.hsv.setHSV(nextColor);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("set hcl", () => {
|
||||
const nextColor = colorlib.HCL.new(1, 2, 3);
|
||||
actions.hcl.setHCL(nextColor);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("set hex", () => {
|
||||
const nextColor = colorlib.Hex.from_code("FF0000");
|
||||
actions.hex.setHex(nextColor);
|
||||
expect(state.color.hex.to_code()).toBe(nextColor.to_code());
|
||||
});
|
||||
});
|
||||
|
||||
describe("set by component", () => {
|
||||
describe("rgb", () => {
|
||||
test("set rgb r", () => {
|
||||
const nextColor = colorlib.RGB.new(100, 0, 0);
|
||||
actions.rgb.setR(nextColor.r);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("set rgb g", () => {
|
||||
const nextColor = colorlib.RGB.new(0, 100, 0);
|
||||
actions.rgb.setG(nextColor.g);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("set rgb b", () => {
|
||||
const nextColor = colorlib.RGB.new(0, 0, 100);
|
||||
actions.rgb.setB(nextColor.b);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hsv", () => {
|
||||
test("set hsv h", () => {
|
||||
const nextColor = colorlib.HSV.new(100, 0, 0);
|
||||
actions.hsv.setH(nextColor.h);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("set hsv s", () => {
|
||||
const nextColor = colorlib.HSV.new(0, 0.5, 0);
|
||||
actions.hsv.setS(nextColor.s);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("set hsv v", () => {
|
||||
const nextColor = colorlib.HSV.new(0, 0, 0.5);
|
||||
actions.hsv.setV(nextColor.v);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hcl", () => {
|
||||
test("set hcl h", () => {
|
||||
const nextColor = colorlib.HCL.new(100, 0, 0);
|
||||
actions.hcl.setH(nextColor.h);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("set hcl c", () => {
|
||||
const nextColor = colorlib.HCL.new(0, 0.5, 0);
|
||||
actions.hcl.setC(nextColor.c);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("set hcl l", () => {
|
||||
const nextColor = colorlib.HCL.new(0, 0, 0.5);
|
||||
actions.hcl.setL(nextColor.l);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("adjust by component", () => {
|
||||
describe("rgb", () => {
|
||||
test("adjust rgb r", () => {
|
||||
let nextColor = colorlib.RGB.new(100, 0, 0);
|
||||
actions.rgb.setR((prev) => prev + 100);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
|
||||
nextColor = colorlib.RGB.new(50, 0, 0);
|
||||
actions.rgb.setR((prev) => prev - 50);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("adjust rgb g", () => {
|
||||
let nextColor = colorlib.RGB.new(0, 100, 0);
|
||||
actions.rgb.setG((prev) => prev + 100);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
|
||||
nextColor = colorlib.RGB.new(0, 50, 0);
|
||||
actions.rgb.setG((prev) => prev - 50);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
|
||||
test("adjust rgb b", () => {
|
||||
let nextColor = colorlib.RGB.new(0, 0, 100);
|
||||
actions.rgb.setB((prev) => prev + 100);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
|
||||
nextColor = colorlib.RGB.new(0, 0, 50);
|
||||
actions.rgb.setB((prev) => prev - 50);
|
||||
expectRGB(state.color.rgb, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hsv", () => {
|
||||
test("adjust hsv h", () => {
|
||||
let nextColor = colorlib.HSV.new(100, 0, 0);
|
||||
actions.hsv.setH((prev) => prev + 100);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
|
||||
nextColor = colorlib.HSV.new(50, 0, 0);
|
||||
actions.hsv.setH((prev) => prev - 50);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hsv s", () => {
|
||||
let nextColor = colorlib.HSV.new(0, 1, 0);
|
||||
actions.hsv.setS((prev) => prev + 1);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
|
||||
nextColor = colorlib.HSV.new(0, 0.5, 0);
|
||||
actions.hsv.setS((prev) => prev - 0.5);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hsv v", () => {
|
||||
let nextColor = colorlib.HSV.new(0, 0, 1);
|
||||
actions.hsv.setV((prev) => prev + 1);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
|
||||
nextColor = colorlib.HSV.new(0, 0, 0.5);
|
||||
actions.hsv.setV((prev) => prev - 0.5);
|
||||
expectHSV(state.color.hsv, nextColor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hcl", () => {
|
||||
test("adjust hcl h", () => {
|
||||
let nextColor = colorlib.HCL.new(100, 0, 0);
|
||||
actions.hcl.setH((prev) => prev + 100);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
|
||||
nextColor = colorlib.HCL.new(50, 0, 0);
|
||||
actions.hcl.setH((prev) => prev - 50);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hcl c", () => {
|
||||
let nextColor = colorlib.HCL.new(0, 1, 0);
|
||||
actions.hcl.setC((prev) => prev + 1);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
|
||||
nextColor = colorlib.HCL.new(0, 0.5, 0);
|
||||
actions.hcl.setC((prev) => prev - 0.5);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
|
||||
test("adjust hcl l", () => {
|
||||
let nextColor = colorlib.HCL.new(0, 0, 1);
|
||||
actions.hcl.setL((prev) => prev + 1);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
|
||||
nextColor = colorlib.HCL.new(0, 0, 0.5);
|
||||
actions.hcl.setL((prev) => prev - 0.5);
|
||||
expectHCL(state.color.hcl, nextColor);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,12 +11,22 @@ function TestSquare() {
|
||||
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [xValue, setXValue] = useState(0);
|
||||
const [yValue, setYValue] = useState(0);
|
||||
const xValueRange = { min: 0, max: 100 };
|
||||
const yValueRange = { min: 0, max: 100 };
|
||||
|
||||
const { crosshairRef, isDragging } = useCrosshair({
|
||||
origin,
|
||||
dimensions,
|
||||
setXPosition,
|
||||
setYPosition,
|
||||
xValue,
|
||||
yValue,
|
||||
setXValue,
|
||||
setYValue,
|
||||
xValueRange,
|
||||
yValueRange,
|
||||
});
|
||||
|
||||
const boundaryRef = useRef<HTMLDivElement>(null);
|
||||
@@ -98,6 +108,10 @@ function TestSquare() {
|
||||
{isDragging ? "True" : "False"}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
X Value: <span data-cy="x-value-display">{xValue}</span>
|
||||
<br />Y Value: <span data-cy="y-value-display">{yValue}</span>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import type { CartesianSpace } from "@/types";
|
||||
import { Direction } from "@/types";
|
||||
import { chooseValueByDirection, valueToPosition } from "@/util";
|
||||
|
||||
import { Direction, useSlider } from "../slider";
|
||||
import { useSlider } from "../slider";
|
||||
|
||||
// Test Fixtures
|
||||
|
||||
@@ -13,13 +15,16 @@ function TestSlider({
|
||||
}) {
|
||||
const [origin, setOrigin] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [position, setPosition] = useState(0);
|
||||
const [sliderValue, setSliderValue] = useState(0);
|
||||
const [value, setValue] = useState(0);
|
||||
const position = useRef(0);
|
||||
const valueRange = { min: 0, max: 100 };
|
||||
const { sliderRef, isDragging } = useSlider({
|
||||
direction,
|
||||
origin,
|
||||
dimensions,
|
||||
setPosition,
|
||||
valueRange,
|
||||
value,
|
||||
setValue,
|
||||
});
|
||||
|
||||
const railRef = useRef<HTMLSpanElement>(null);
|
||||
@@ -41,12 +46,23 @@ function TestSlider({
|
||||
const maxValue =
|
||||
direction == Direction.HORIZONTAL ? dimensions.x : dimensions.y;
|
||||
if (maxValue > 0) {
|
||||
const percentage = parseFloat(((position / maxValue) * 100).toFixed(3));
|
||||
setSliderValue(percentage);
|
||||
const percentage = parseFloat(
|
||||
((position.current / maxValue) * 100).toFixed(3),
|
||||
);
|
||||
setValue(percentage);
|
||||
} else {
|
||||
setSliderValue(0);
|
||||
setValue(0);
|
||||
}
|
||||
}, [dimensions, direction, position]);
|
||||
}, [dimensions, direction]);
|
||||
|
||||
useEffect(() => {
|
||||
const maxPosition = chooseValueByDirection(
|
||||
direction,
|
||||
dimensions.x,
|
||||
dimensions.y,
|
||||
);
|
||||
position.current = valueToPosition(value, maxPosition, valueRange);
|
||||
}, [value, direction, dimensions, valueRange]);
|
||||
|
||||
const isHorizontal = direction === Direction.HORIZONTAL;
|
||||
|
||||
@@ -82,8 +98,8 @@ function TestSlider({
|
||||
style={{
|
||||
position: "absolute",
|
||||
...(isHorizontal
|
||||
? { left: position, top: 0 }
|
||||
: { left: 0, top: position }),
|
||||
? { left: position.current, top: 0 }
|
||||
: { left: 0, top: position.current }),
|
||||
width: 50,
|
||||
height: 50,
|
||||
background: "rgba(255,0,0,0.5)",
|
||||
@@ -92,8 +108,9 @@ function TestSlider({
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
Position: <span data-cy="position-display">{position}</span>px
|
||||
<br /> Value: <span data-cy="value-display">{sliderValue}</span>
|
||||
Value: <span data-cy="value-display">{value}</span>
|
||||
<br /> Position:{" "}
|
||||
<span data-cy="position-display">{position.current}</span>px
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user