import { useReducer } from "react"; import type { Adjustments, ExportFormat, PixelRect, Template } from "../types"; import { NEUTRAL_ADJUSTMENTS } from "../types"; import { DEFAULT_TEMPLATE } from "../templates"; export const CUSTOM_TEMPLATE_ID = "custom"; export interface EditorState { crop: { x: number; y: number }; zoom: number; rotation: number; flipH: boolean; flipV: boolean; aspect: number | null; output: { width: number; height: number } | null; maxEdge: number | null; adjustments: Adjustments; templateId: string; format: ExportFormat; quality: number; croppedAreaPixels: PixelRect | null; } function fromTemplate(t: Template): EditorState { return { crop: { x: 0, y: 0 }, zoom: 1, rotation: 0, flipH: false, flipV: false, aspect: t.aspect, output: t.output, maxEdge: null, adjustments: { ...NEUTRAL_ADJUSTMENTS }, templateId: t.id, format: t.encoder.format, quality: t.encoder.quality, croppedAreaPixels: null, }; } export const initialEditorState = fromTemplate(DEFAULT_TEMPLATE); function wrap360(deg: number): number { return ((deg % 360) + 360) % 360; } export type EditorAction = | { type: "selectTemplate"; template: Template } | { type: "setAspect"; aspect: number | null } | { type: "setCrop"; crop: { x: number; y: number } } | { type: "setZoom"; zoom: number } | { type: "setRotation"; rotation: number } | { type: "rotateBy"; delta: number } | { type: "toggleFlipH" } | { type: "toggleFlipV" } | { type: "setAdjustment"; key: keyof Adjustments; value: number } | { type: "setAdjustments"; adjustments: Adjustments } | { type: "resetAdjustments" } | { type: "setMaxEdge"; maxEdge: number | null } | { type: "setFormat"; format: ExportFormat } | { type: "setQuality"; quality: number } | { type: "setCroppedAreaPixels"; rect: PixelRect } | { type: "resetEdits" }; function reducer(state: EditorState, action: EditorAction): EditorState { switch (action.type) { case "selectTemplate": { const t = action.template; return { ...state, templateId: t.id, aspect: t.aspect, output: t.output, format: t.encoder.format, quality: t.encoder.quality, crop: { x: 0, y: 0 }, zoom: 1, }; } case "setAspect": return { ...state, aspect: action.aspect, output: null, templateId: CUSTOM_TEMPLATE_ID, crop: { x: 0, y: 0 }, zoom: 1, }; case "setCrop": return { ...state, crop: action.crop }; case "setZoom": return { ...state, zoom: action.zoom }; case "setRotation": return { ...state, rotation: wrap360(action.rotation) }; case "rotateBy": return { ...state, rotation: wrap360(state.rotation + action.delta) }; case "toggleFlipH": return { ...state, flipH: !state.flipH }; case "toggleFlipV": return { ...state, flipV: !state.flipV }; case "setAdjustment": return { ...state, adjustments: { ...state.adjustments, [action.key]: action.value } }; case "setAdjustments": return { ...state, adjustments: action.adjustments }; case "resetAdjustments": return { ...state, adjustments: { ...NEUTRAL_ADJUSTMENTS } }; case "setMaxEdge": return { ...state, maxEdge: action.maxEdge }; case "setFormat": return { ...state, format: action.format }; case "setQuality": return { ...state, quality: action.quality }; case "setCroppedAreaPixels": return { ...state, croppedAreaPixels: action.rect }; case "resetEdits": return { ...state, crop: { x: 0, y: 0 }, zoom: 1, rotation: 0, flipH: false, flipV: false, adjustments: { ...NEUTRAL_ADJUSTMENTS }, }; } } export function useEditor() { return useReducer(reducer, initialEditorState); }