@yogiswara/honcho-editor-ui 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ // "use client";
3
+ // import React from "react";
4
+ // import { Box } from "@mui/material";
5
+ // import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
6
+ // import ImageItem from "../GalleryAlbum/ImageItem";
7
+ // import { GallerySetup } from "../../../hooks/editor/type";
8
+ // interface ImageGalleryProps {
9
+ // imageCollection: GallerySetup[];
10
+ // isSelectedMode: boolean;
11
+ // isHiddenGallery: boolean;
12
+ // onPreview: (photo: unknown) => () => void;
13
+ // onSelectedMode: () => void;
14
+ // onToggleSelect: (photo: unknown) => () => void;
15
+ // }
16
+ // const AlbumImageGallery: React.FC<ImageGalleryProps> = (props) => {
17
+ // const {
18
+ // imageCollection,
19
+ // isSelectedMode,
20
+ // isHiddenGallery,
21
+ // onPreview,
22
+ // onSelectedMode,
23
+ // onToggleSelect,
24
+ // } = props;
25
+ // return (
26
+ // <section>
27
+ // <ResponsiveMasonry columnsCountBreakPoints={{ 750: 2, 900: 4 }}>
28
+ // <Masonry>
29
+ // {imageCollection.map((photo, index) => (
30
+ // <Box key={photo.key} sx={{ m: 0.5 }}>
31
+ // <ImageItem
32
+ // margin="0px"
33
+ // index={index}
34
+ // photo={photo}
35
+ // direction="column"
36
+ // isFullScreenMode={false}
37
+ // isSelected={photo.isSelected}
38
+ // isSelectedMode={isSelectedMode}
39
+ // isHiddenGallery={isHiddenGallery}
40
+ // onPreview={onPreview(photo)}
41
+ // onSelectedMode={onSelectedMode}
42
+ // onToggleSelect={onToggleSelect(photo)}
43
+ // frame={photo.frame}
44
+ // />
45
+ // </Box>
46
+ // ))}
47
+ // </Masonry>
48
+ // </ResponsiveMasonry>
49
+ // </section>
50
+ // );
51
+ // };
52
+ // export default AlbumImageGallery;
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ // import { CSSProperties, useMemo } from "react";
3
+ // import { Box, CardMedia, useTheme } from "@mui/material";
4
+ // import { TickCircle } from "iconsax-react";
5
+ // import CustomTickIcon from "@/assets/svg/Tick";
6
+ // import { useImageProcessor } from "@/lib/hooks/useImageProcessor";
7
+ // import type { AdjustmentValues } from "@/lib/honcho-editor";
8
+ // import { GallerySetup } from "../../../hooks/editor/type";
9
+ // import { neutral } from "@/color";
10
+ // import { log } from "@/utils/logger";
11
+ // const imgStyle = {
12
+ // transition: "transform .135s cubic-bezier(0.0,0.0,0.2,1),opacity linear .15s",
13
+ // width: "100%",
14
+ // // objectFit: "contain",
15
+ // //height: "100%",
16
+ // };
17
+ // const selectedImgStyle = {
18
+ // borderRadius: "8px",
19
+ // transform: "translateZ(0px) scale3d(1, 1, 1)",
20
+ // // transition: "transform .135s cubic-bezier(0.0,0.0,0.2,1),opacity linear .15s"
21
+ // };
22
+ // interface PhotoProps<T> {
23
+ // src: string;
24
+ // alt: string;
25
+ // width: number;
26
+ // height: number;
27
+ // key: string;
28
+ // // other properties
29
+ // photo?: T;
30
+ // }
31
+ // interface Props {
32
+ // margin?: any;
33
+ // index: number;
34
+ // photo: PhotoProps<GallerySetup>;
35
+ // // onClick: renderImageClickHandler | null
36
+ // direction: "row" | "column";
37
+ // isSelectedMode: boolean;
38
+ // isFullScreenMode: boolean; // this preview Mode we plan to use this component on there
39
+ // isSelected: boolean;
40
+ // isHiddenGallery: boolean;
41
+ // onToggleSelect: () => void;
42
+ // onPreview: () => void;
43
+ // onSelectedMode: () => void;
44
+ // // NEW: Editor interceptor props
45
+ // enableEditor?: boolean;
46
+ // adjustments?: Partial<AdjustmentValues>;
47
+ // frame?: string | null;
48
+ // }
49
+ // const ImageItem = (props: Props) => {
50
+ // const { photo, margin, enableEditor = true, adjustments, frame } = props;
51
+ // const theme = useTheme();
52
+ // // Memoize adjustments to prevent infinite re-renders
53
+ // const memoizedAdjustments = useMemo(() => ({
54
+ // ...adjustments,
55
+ // exposure: 50,
56
+ // }), [adjustments]);
57
+ // // Use the image processor hook
58
+ // const { processedImageSrc, isProcessingComplete } = useImageProcessor({
59
+ // photoId: photo.key,
60
+ // photoSrc: photo.src,
61
+ // enableEditor,
62
+ // adjustments: memoizedAdjustments,
63
+ // frame
64
+ // });
65
+ // const styleHiddenGallery: CSSProperties = useMemo(() => {
66
+ // if (props.isHiddenGallery) {
67
+ // return { filter: "blur(20px)", pointerEvents: "none" };
68
+ // } else {
69
+ // return {};
70
+ // }
71
+ // }, [props.isHiddenGallery]);
72
+ // const commonStyle = useMemo(() => {
73
+ // return {
74
+ // backgroundColor: neutral.white,
75
+ // overflow: "hidden",
76
+ // position: "relative",
77
+ // width: "100%",
78
+ // aspectRatio: `${photo.width}/${photo.height}`,
79
+ // };
80
+ // }, [props.direction, props.photo]);
81
+ // const handleImageClick = () => {
82
+ // log.debug(
83
+ // {
84
+ // isFullMode: props.isFullScreenMode,
85
+ // isShowGallery: props.isHiddenGallery,
86
+ // },
87
+ // "handleImageClick"
88
+ // );
89
+ // if (!props.isFullScreenMode && !props.isHiddenGallery) {
90
+ // if (props.isSelectedMode) {
91
+ // log.debug("handleImageClick with toggle select");
92
+ // props.onToggleSelect();
93
+ // } else {
94
+ // log.debug("handleImageClick with preview");
95
+ // props.onPreview();
96
+ // }
97
+ // }
98
+ // };
99
+ // const handleImageSelectedIconClick = () => {
100
+ // log.debug("handleImageSelectedIconClick");
101
+ // if (!props.isFullScreenMode) {
102
+ // if (!props.isSelectedMode) {
103
+ // props.onSelectedMode();
104
+ // }
105
+ // props.onToggleSelect();
106
+ // }
107
+ // };
108
+ // const imageSx = useMemo(() => {
109
+ // const baseStyles = props.isSelected ? {
110
+ // ...imgStyle,
111
+ // ...selectedImgStyle,
112
+ // ...styleHiddenGallery,
113
+ // aspectRatio: `${photo.width}/${photo.height}`,
114
+ // } : {
115
+ // ...imgStyle,
116
+ // ...styleHiddenGallery,
117
+ // };
118
+ // return {
119
+ // ...baseStyles,
120
+ // opacity: isProcessingComplete ? 1 : 0,
121
+ // transition: 'opacity 0.3s ease-in-out',
122
+ // };
123
+ // }, [props.isSelected, styleHiddenGallery, photo.width, photo.height, isProcessingComplete]);
124
+ // const boxNotSelected = useMemo(
125
+ // () => ({
126
+ // margin,
127
+ // // height: photo.height,
128
+ // width: "100%",
129
+ // ...commonStyle,
130
+ // transition: ".3s",
131
+ // // "&:hover": { padding: "12px", backgroundColor: "primary.light1" },
132
+ // "&:-webkit-transition": { transition: ".3s" },
133
+ // "&:hover > .checkbox": { display: props.isHiddenGallery ? "" : "block" },
134
+ // cursor:
135
+ // props.isFullScreenMode || props.isHiddenGallery ? "inherit" : "pointer",
136
+ // backgroundColor: props.isFullScreenMode
137
+ // ? "rgba(0,0,0,0.1)"
138
+ // : "transparent",
139
+ // }),
140
+ // [margin, commonStyle, props.isHiddenGallery, props.isFullScreenMode]
141
+ // );
142
+ // const boxSelected = useMemo(
143
+ // () => ({
144
+ // margin,
145
+ // // height: photo.height,
146
+ // width: "100%",
147
+ // ...commonStyle,
148
+ // cursor:
149
+ // props.isFullScreenMode || props.isHiddenGallery ? "inherit" : "pointer",
150
+ // transition: ".3s",
151
+ // "&:-webkit-transition": { transition: ".3s" },
152
+ // padding: { xs: "13px 12px", sm: "21.31px 25.56px 21.32px 27.68px" },
153
+ // backgroundColor: theme.palette.light["Surface-Variant-2"],
154
+ // }),
155
+ // [
156
+ // margin,
157
+ // commonStyle,
158
+ // theme.palette.light,
159
+ // props.isFullScreenMode,
160
+ // props.isHiddenGallery,
161
+ // ]
162
+ // );
163
+ // const boxOuterSx = useMemo(() => {
164
+ // if (props.isFullScreenMode) {
165
+ // return {
166
+ // margin,
167
+ // width: "100%",
168
+ // ...commonStyle,
169
+ // };
170
+ // } else {
171
+ // if (props.isSelected) {
172
+ // return { ...boxSelected };
173
+ // } else {
174
+ // return { ...boxNotSelected };
175
+ // }
176
+ // }
177
+ // }, [
178
+ // props.isFullScreenMode,
179
+ // photo.height,
180
+ // photo.width,
181
+ // commonStyle,
182
+ // boxSelected,
183
+ // boxNotSelected,
184
+ // margin,
185
+ // props.isSelected,
186
+ // ]);
187
+ // return (
188
+ // <Box id={"Box_image"} key={photo.key} sx={boxOuterSx} className={"image"}>
189
+ // {!props.isHiddenGallery &&
190
+ // (props.isSelected ? (
191
+ // <Box
192
+ // color={"primary.dark1"}
193
+ // onClick={handleImageSelectedIconClick}
194
+ // sx={{
195
+ // position: "absolute",
196
+ // width: "19px",
197
+ // height: "19px",
198
+ // zIndex: "2",
199
+ // left: "5px",
200
+ // top: "5px",
201
+ // borderRadius: { xs: "50%", sm: 0 },
202
+ // }}
203
+ // className={"checkbox"}
204
+ // >
205
+ // <CustomTickIcon />
206
+ // </Box>
207
+ // ) : (
208
+ // <Box
209
+ // color={"neutral.light2"}
210
+ // onClick={handleImageSelectedIconClick}
211
+ // sx={{
212
+ // position: "absolute",
213
+ // width: "19px",
214
+ // height: "19px",
215
+ // zIndex: "1",
216
+ // left: "5px",
217
+ // top: "5px",
218
+ // display: "none",
219
+ // visibility: {
220
+ // xs: props.isSelectedMode ? "visible" : "hidden",
221
+ // sm: "visible",
222
+ // },
223
+ // }}
224
+ // className={"checkbox"}
225
+ // >
226
+ // <TickCircle
227
+ // style={{ width: "24px", height: "24px" }}
228
+ // color="white"
229
+ // />
230
+ // </Box>
231
+ // ))}
232
+ // <CardMedia
233
+ // id="card_media"
234
+ // component="img"
235
+ // className="image"
236
+ // loading="lazy"
237
+ // alt={photo.alt}
238
+ // sx={imageSx}
239
+ // src={processedImageSrc}
240
+ // width="100%"
241
+ // onClick={handleImageClick}
242
+ // />
243
+ // </Box>
244
+ // );
245
+ // };
246
+ // export default ImageItem;
@@ -74,7 +74,7 @@ export default function HSliderLightMobile(props) {
74
74
  '& .MuiSlider-thumb:hover': {
75
75
  boxShadow: 'none',
76
76
  }
77
- }, size: "small", value: props.exposureScore, step: 0.1, min: -100, max: 100, onChange: (_event, newValue) => props.onExposureChange(newValue) }), _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { pt: '10px', pb: '0px' }, children: [_jsx(Typography, { sx: { ...typography.bodyMedium, color: colors.surface }, children: "Contrast" }), _jsx(TextField, { hiddenLabel: true, id: "filled-hidden-label-small", value: formatValue(props.contrastScore), variant: "filled", onChange: (e) => handleInputChange(e, -100, 100, props.onContrastChange), sx: {
77
+ }, size: "small", value: props.exposureScore, step: 1, min: -100, max: 100, onChange: (_event, newValue) => props.onExposureChange(newValue) }), _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { pt: '10px', pb: '0px' }, children: [_jsx(Typography, { sx: { ...typography.bodyMedium, color: colors.surface }, children: "Contrast" }), _jsx(TextField, { hiddenLabel: true, id: "filled-hidden-label-small", value: formatValue(props.contrastScore), variant: "filled", onChange: (e) => handleInputChange(e, -100, 100, props.onContrastChange), sx: {
78
78
  width: "40px",
79
79
  alignItems: "center",
80
80
  textAlign: "right",
@@ -44,6 +44,18 @@ export interface EditorConfig {
44
44
  transformation_adjustment: TransformationAdjustment[];
45
45
  watermarks: Watermark[];
46
46
  }
47
+ export interface GallerySetup {
48
+ src: string;
49
+ original: string;
50
+ srcSet?: string | string[] | undefined;
51
+ sizes?: string | string[] | undefined;
52
+ width: number;
53
+ height: number;
54
+ alt: string | undefined;
55
+ key: string | undefined;
56
+ frame?: string | undefined;
57
+ isSelected?: boolean | undefined;
58
+ }
47
59
  export interface Gallery {
48
60
  id: string;
49
61
  uid: string;
@@ -8,7 +8,6 @@ const initialAdjustments = {
8
8
  tempScore: 0, tintScore: 0, vibranceScore: 0, exposureScore: 0, highlightsScore: 0, shadowsScore: 0,
9
9
  whitesScore: 0, blacksScore: 0, saturationScore: 0, contrastScore: 0, clarityScore: 0, sharpnessScore: 0,
10
10
  };
11
- // const clamp = (value: number) => Math.max(-100, Math.min(100, value));
12
11
  export function useHonchoEditor(controller, initImageId, firebaseUid) {
13
12
  const { onSwipeNext, onSwipePrev, isNextAvailable, isPrevAvailable, isLoading: isGalleryLoading, error: galleryError, currentImageData: galleryImageData } = useGallerySwipe(firebaseUid, initImageId, controller);
14
13
  // The useAdjustmentHistory hook now manages all undo/redo and adjustment state logic.
@@ -52,7 +51,6 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
52
51
  const [isPresetCreated, setIsPresetCreated] = useState(false);
53
52
  const [selectedMobilePreset, setSelectedMobilePreset] = useState('preset1');
54
53
  const [selectedDesktopPreset, setSelectedDesktopPreset] = useState('preset1');
55
- // const [selectedBulkPreset, setSelectedBulkPreset] = useState<string>('preset1');
56
54
  const [presetMenuAnchorEl, setPresetMenuAnchorEl] = useState(null);
57
55
  const [activePresetMenuId, setActivePresetMenuId] = useState(null);
58
56
  const [isRenameModalOpen, setRenameModalOpen] = useState(false);
@@ -90,30 +88,6 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
90
88
  const contentRef = useRef(null);
91
89
  // Effect for keyboard shortcuts
92
90
  // MARK: - Core Editor Logic
93
- // MARK: Batch Edit logic
94
- // const handleToggleImageSelection = useCallback((imageId: string) => {
95
- // const newSelectedIds = new Set(selectedImageIds);
96
- // const isCurrentlySelected = newSelectedIds.has(imageId);
97
- // if (isCurrentlySelected) {
98
- // if (newSelectedIds.size > 1) {
99
- // newSelectedIds.delete(imageId);
100
- // }
101
- // } else {
102
- // newSelectedIds.add(imageId);
103
- // // Apply the current UI's adjustments to the newly selected image.
104
- // setAdjustmentsMap(prevMap => {
105
- // const newMap = new Map(prevMap);
106
- // const currentUiState = {
107
- // tempScore, tintScore, vibranceScore, saturationScore,
108
- // exposureScore, highlightsScore, shadowsScore, whitesScore,
109
- // blacksScore, contrastScore, clarityScore, sharpnessScore
110
- // };
111
- // newMap.set(imageId, currentUiState);
112
- // return newMap;
113
- // });
114
- // }
115
- // setSelectedImageIds(newSelectedIds);
116
- // }, [selectedImageIds, tempScore, tintScore, vibranceScore, saturationScore, exposureScore, highlightsScore, shadowsScore, whitesScore, blacksScore, contrastScore, clarityScore, sharpnessScore]);
117
91
  // Mobile Panel Drag Handlers
118
92
  const handleContentHeightChange = useCallback((height) => {
119
93
  if (height > 0 && height !== contentHeight)
@@ -164,7 +138,7 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
164
138
  return;
165
139
  if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
166
140
  event.preventDefault();
167
- handleOpenCopyDialog(); // Assumes handleOpenCopyDialog is defined in the hook
141
+ handleOpenCopyDialog();
168
142
  }
169
143
  }, [ /* handleOpenCopyDialog dependency */]);
170
144
  const extractPathFromGallery = useCallback((data) => {
@@ -224,7 +198,6 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
224
198
  return;
225
199
  controller.handleBack(firebaseUid, galleryImageData.id);
226
200
  }, [controller, firebaseUid, galleryImageData]);
227
- // MARK: - UI Handlers (Moved from page.tsx)
228
201
  // MARK: - UI Handlers
229
202
  // Panel Handlers
230
203
  const handleColorAccordionChange = (panel) => (_, isExpanded) => {
@@ -1,6 +1,16 @@
1
1
  import { SelectChangeEvent } from "@mui/material";
2
- import { AdjustmentState, ImageItem, Controller } from './useHonchoEditor';
3
- export declare function useHonchoEditorBulk(controller: Controller, initImageId: string, firebaseUid: string): {
2
+ import { AdjustmentState, ImageItem, Controller, Preset } from './useHonchoEditor';
3
+ import { Gallery, ResponseGalleryPaging } from '../../hooks/editor/type';
4
+ export interface ControllerBulk {
5
+ onGetImage(firebaseUid: string, imageID: string): Promise<Gallery>;
6
+ getImageList(firebaseUid: string, eventID: string, page: number): Promise<ResponseGalleryPaging>;
7
+ syncConfig(firebaseUid: string): Promise<void>;
8
+ handleBack(firebaseUid: string, eventID: string): void;
9
+ getPresets(firebaseUid: string): Promise<Preset[]>;
10
+ createPreset(firebaseUid: string, name: string, settings: AdjustmentState): Promise<Preset>;
11
+ deletePreset(firebaseUid: string, presetId: string): Promise<void>;
12
+ }
13
+ export declare function useHonchoEditorBulk(controllerBulk: Controller, eventID: string, firebaseUid: string): {
4
14
  isBulkEditing: boolean;
5
15
  selectedImages: string;
6
16
  imageList: ImageItem[];
@@ -11,6 +21,7 @@ export declare function useHonchoEditorBulk(controller: Controller, initImageId:
11
21
  handleToggleImageSelection: (imageId: string) => void;
12
22
  toggleBulkEditing: () => void;
13
23
  handleSelectBulkPreset: (event: SelectChangeEvent<string>) => void;
24
+ handleBackCallbackBulk: () => void;
14
25
  setTempScore: (value: number) => void;
15
26
  setTintScore: (value: number) => void;
16
27
  setVibranceScore: (value: number) => void;
@@ -1,13 +1,18 @@
1
1
  'use client';
2
2
  import { useState, useCallback, useEffect } from 'react';
3
3
  import { useAdjustmentHistory } from '../useAdjustmentHistory';
4
+ import { useAdjustmentHistoryBatch } from '../useAdjustmentHistoryBatch';
4
5
  const initialAdjustments = {
5
6
  tempScore: 0, tintScore: 0, vibranceScore: 0, exposureScore: 0, highlightsScore: 0, shadowsScore: 0,
6
7
  whitesScore: 0, blacksScore: 0, saturationScore: 0, contrastScore: 0, clarityScore: 0, sharpnessScore: 0,
7
8
  };
8
9
  const clamp = (value) => Math.max(-100, Math.min(100, value));
9
- export function useHonchoEditorBulk(controller, initImageId, firebaseUid) {
10
+ export function useHonchoEditorBulk(controllerBulk, eventID, firebaseUid) {
10
11
  const { currentState, actions: historyActions, historyInfo } = useAdjustmentHistory(initialAdjustments);
12
+ const { currentBatch, selectedIds, allImageIds, actions: batchActions, } = useAdjustmentHistoryBatch({
13
+ maxSize: historyInfo.historySize,
14
+ defaultAdjustmentState: currentState,
15
+ });
11
16
  // State for Bulk Editing
12
17
  const [isBulkEditing, setIsBulkEditing] = useState(false);
13
18
  const [selectedImages, setSelectedImages] = useState('Select');
@@ -15,6 +20,11 @@ export function useHonchoEditorBulk(controller, initImageId, firebaseUid) {
15
20
  const [selectedImageIds, setSelectedImageIds] = useState(new Set());
16
21
  const [adjustmentsMap, setAdjustmentsMap] = useState(new Map());
17
22
  const [selectedBulkPreset, setSelectedBulkPreset] = useState('preset1');
23
+ const handleBackCallbackBulk = useCallback(() => {
24
+ if (!eventID)
25
+ return;
26
+ controllerBulk.handleBack(firebaseUid, eventID);
27
+ }, [controllerBulk, firebaseUid, eventID]);
18
28
  const handleFileChangeBulk = (event) => {
19
29
  const files = event.target?.files;
20
30
  if (!files || files.length <= 1) {
@@ -168,6 +178,7 @@ export function useHonchoEditorBulk(controller, initImageId, firebaseUid) {
168
178
  handleToggleImageSelection,
169
179
  toggleBulkEditing,
170
180
  handleSelectBulkPreset,
181
+ handleBackCallbackBulk,
171
182
  // Bulk Adjustment Handlers
172
183
  setTempScore,
173
184
  setTintScore,
@@ -1,9 +1,28 @@
1
1
  import { AdjustmentState } from './editor/useHonchoEditor';
2
+ /**
3
+ * Configuration for image with adjustment state
4
+ */
5
+ export interface ImageAdjustmentConfig {
6
+ imageId: string;
7
+ /** Adjustment state for this image. If not provided, uses default (all zeros) */
8
+ adjustment?: AdjustmentState;
9
+ }
2
10
  /**
3
11
  * Batch adjustment state - maps image IDs to their adjustment states
4
12
  */
5
13
  export interface BatchAdjustmentState {
6
- [imageId: string]: AdjustmentState;
14
+ /** Currently selected images that are being actively adjusted */
15
+ currentSelection: {
16
+ [imageId: string]: AdjustmentState;
17
+ };
18
+ /** All images that have been selected/adjusted before (persistent state) */
19
+ allImages: {
20
+ [imageId: string]: AdjustmentState;
21
+ };
22
+ /** Track initial/baseline state for each image */
23
+ initialStates: {
24
+ [imageId: string]: AdjustmentState;
25
+ };
7
26
  }
8
27
  /**
9
28
  * Configuration options for the batch adjustment history hook
@@ -41,16 +60,16 @@ export interface BatchHistoryInfo {
41
60
  export interface BatchHistoryActions {
42
61
  /** Apply adjustment deltas to selected images */
43
62
  adjustSelected: (delta: Partial<AdjustmentState>) => void;
44
- /** Set specific adjustment states for specified images */
45
- setAdjustments: (adjustments: Partial<BatchAdjustmentState>) => void;
46
63
  /** Undo last changes to selected images */
47
64
  undo: () => void;
48
65
  /** Redo next changes to selected images */
49
66
  redo: () => void;
50
67
  /** Reset selected images to default state */
51
68
  reset: (imageIds?: string[]) => void;
52
- /** Set which images are selected */
53
- setSelection: (imageIds: string[]) => void;
69
+ /** Set selection with optional initial adjustments per image */
70
+ setSelection: (configs: ImageAdjustmentConfig[]) => void;
71
+ /** Sync/replace adjustment for images (clears their history) */
72
+ syncAdjustment: (configs: ImageAdjustmentConfig[]) => void;
54
73
  /** Add or remove image from selection */
55
74
  toggleSelection: (imageId: string) => void;
56
75
  /** Select all images */
@@ -74,10 +93,6 @@ export interface BatchHistoryConfig {
74
93
  setMaxSize: (size: number | 'unlimited') => void;
75
94
  /** Get current memory usage estimate */
76
95
  getMemoryUsage: () => number;
77
- /** Add new images to the batch */
78
- addImages: (imageIds: string[], selectNew?: boolean) => void;
79
- /** Remove images from the batch */
80
- removeImages: (imageIds: string[]) => void;
81
96
  }
82
97
  /**
83
98
  * Return type for the useAdjustmentHistoryBatch hook
@@ -99,16 +114,45 @@ export interface UseAdjustmentHistoryBatchReturn {
99
114
  /**
100
115
  * Advanced hook for managing batch AdjustmentState history with selective undo/redo functionality.
101
116
  *
102
- * Features:
103
- * - Manages multiple images with individual adjustment states
104
- * - Selective operations that only affect selected images
105
- * - Proper history tracking for batch operations
106
- * - Memory usage monitoring and optimization
107
- * - Flexible selection management
117
+ * **Pure State Management Design:**
118
+ * - Starts empty, no image loading functionality
119
+ * - Focus on state management and history tracking only
120
+ * - Selection-based operations with persistent state
121
+ * - Manual selection via `actions.setSelection()`
122
+ *
123
+ * **Key Features:**
124
+ * - **Current Selection**: Images actively being adjusted
125
+ * - **All Images**: Persistent state for every image touched
126
+ * - **Selective Operations**: Undo/redo only affects selected images
127
+ * - **Automatic State Persistence**: Images remain in allImages even when deselected
128
+ *
129
+ * **Typical Usage Flow:**
130
+ * ```typescript
131
+ * const { actions, currentBatch, selectedIds } = useAdjustmentHistoryBatch();
132
+ *
133
+ * // Select images (new images get default state automatically)
134
+ * actions.setSelection(['img1', 'img2', 'img3']);
135
+ *
136
+ * // Apply adjustments to selected images
137
+ * actions.adjustSelected({ exposureScore: 10 });
138
+ *
139
+ * // Change selection (img1, img2 state persists in allImages)
140
+ * actions.setSelection(['img3']);
141
+ *
142
+ * // Adjust only img3
143
+ * actions.adjustSelected({ contrastScore: 5 });
144
+ *
145
+ * // Undo affects only currently selected (img3)
146
+ * actions.undo();
147
+ * ```
148
+ *
149
+ * **State Structure:**
150
+ * - `currentBatch.currentSelection`: Currently selected images and their states
151
+ * - `currentBatch.allImages`: All images that have been selected/adjusted (persistent)
152
+ * - `selectedIds`: Array of currently selected image IDs
108
153
  *
109
- * @param imageIds - Array of image IDs to manage
110
154
  * @param options - Configuration options for history behavior
111
155
  * @returns Object with current batch, selection, history info, actions, and config
112
156
  */
113
- export declare function useAdjustmentHistoryBatch(imageIds: string[], options?: BatchHistoryOptions): UseAdjustmentHistoryBatchReturn;
157
+ export declare function useAdjustmentHistoryBatch(options?: BatchHistoryOptions): UseAdjustmentHistoryBatchReturn;
114
158
  export {};
@@ -21,59 +21,66 @@ const createDefaultAdjustmentState = (overrides) => ({
21
21
  * Compare two BatchAdjustmentState objects for equality
22
22
  */
23
23
  const compareBatchStates = (a, b) => {
24
- const aKeys = Object.keys(a);
25
- const bKeys = Object.keys(b);
26
- if (aKeys.length !== bKeys.length)
24
+ try {
25
+ return JSON.stringify(a) === JSON.stringify(b);
26
+ }
27
+ catch (error) {
28
+ console.warn('Failed to compare batch states with JSON.stringify:', error);
27
29
  return false;
28
- for (const key of aKeys) {
29
- if (!b[key])
30
- return false;
31
- try {
32
- if (JSON.stringify(a[key]) !== JSON.stringify(b[key]))
33
- return false;
34
- }
35
- catch (error) {
36
- // Fallback comparison
37
- const stateA = a[key];
38
- const stateB = b[key];
39
- if (stateA.tempScore !== stateB.tempScore ||
40
- stateA.tintScore !== stateB.tintScore ||
41
- stateA.vibranceScore !== stateB.vibranceScore ||
42
- stateA.saturationScore !== stateB.saturationScore ||
43
- stateA.exposureScore !== stateB.exposureScore ||
44
- stateA.highlightsScore !== stateB.highlightsScore ||
45
- stateA.shadowsScore !== stateB.shadowsScore ||
46
- stateA.whitesScore !== stateB.whitesScore ||
47
- stateA.blacksScore !== stateB.blacksScore ||
48
- stateA.contrastScore !== stateB.contrastScore ||
49
- stateA.clarityScore !== stateB.clarityScore ||
50
- stateA.sharpnessScore !== stateB.sharpnessScore) {
51
- return false;
52
- }
53
- }
54
30
  }
55
- return true;
56
31
  };
32
+ /**
33
+ * Create empty batch state
34
+ */
35
+ const createEmptyBatchState = () => ({
36
+ currentSelection: {},
37
+ allImages: {},
38
+ initialStates: {}
39
+ });
57
40
  /**
58
41
  * Advanced hook for managing batch AdjustmentState history with selective undo/redo functionality.
59
42
  *
60
- * Features:
61
- * - Manages multiple images with individual adjustment states
62
- * - Selective operations that only affect selected images
63
- * - Proper history tracking for batch operations
64
- * - Memory usage monitoring and optimization
65
- * - Flexible selection management
43
+ * **Pure State Management Design:**
44
+ * - Starts empty, no image loading functionality
45
+ * - Focus on state management and history tracking only
46
+ * - Selection-based operations with persistent state
47
+ * - Manual selection via `actions.setSelection()`
48
+ *
49
+ * **Key Features:**
50
+ * - **Current Selection**: Images actively being adjusted
51
+ * - **All Images**: Persistent state for every image touched
52
+ * - **Selective Operations**: Undo/redo only affects selected images
53
+ * - **Automatic State Persistence**: Images remain in allImages even when deselected
54
+ *
55
+ * **Typical Usage Flow:**
56
+ * ```typescript
57
+ * const { actions, currentBatch, selectedIds } = useAdjustmentHistoryBatch();
58
+ *
59
+ * // Select images (new images get default state automatically)
60
+ * actions.setSelection(['img1', 'img2', 'img3']);
61
+ *
62
+ * // Apply adjustments to selected images
63
+ * actions.adjustSelected({ exposureScore: 10 });
64
+ *
65
+ * // Change selection (img1, img2 state persists in allImages)
66
+ * actions.setSelection(['img3']);
67
+ *
68
+ * // Adjust only img3
69
+ * actions.adjustSelected({ contrastScore: 5 });
70
+ *
71
+ * // Undo affects only currently selected (img3)
72
+ * actions.undo();
73
+ * ```
74
+ *
75
+ * **State Structure:**
76
+ * - `currentBatch.currentSelection`: Currently selected images and their states
77
+ * - `currentBatch.allImages`: All images that have been selected/adjusted (persistent)
78
+ * - `selectedIds`: Array of currently selected image IDs
66
79
  *
67
- * @param imageIds - Array of image IDs to manage
68
80
  * @param options - Configuration options for history behavior
69
81
  * @returns Object with current batch, selection, history info, actions, and config
70
82
  */
71
- export function useAdjustmentHistoryBatch(imageIds, options = {}) {
72
- // Stabilize imageIds to prevent unnecessary re-renders
73
- const stableImageIds = useMemo(() => {
74
- // Return same reference if content is identical
75
- return imageIds;
76
- }, [imageIds.join(',')]); // Only re-memoize if actual content changes
83
+ export function useAdjustmentHistoryBatch(options = {}) {
77
84
  // Internal stabilization
78
85
  const internalOptions = useMemo(() => ({
79
86
  maxSize: options.maxSize ?? 'unlimited',
@@ -84,20 +91,12 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
84
91
  options.devWarnings,
85
92
  options.defaultAdjustmentState
86
93
  ]);
87
- // Initialize batch state with all images
88
- const createInitialBatch = useCallback((ids) => {
89
- const batch = {};
90
- for (const id of ids) {
91
- batch[id] = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
92
- }
93
- return batch;
94
- }, [internalOptions.defaultAdjustmentState]);
95
- // Core state management - use stable imageIds for initialization
96
- const [allImageIds, setAllImageIds] = useState(stableImageIds);
97
- const [selectedIds, setSelectedIds] = useState(stableImageIds); // Default select all
98
- const [history, setHistory] = useState(() => [createInitialBatch(stableImageIds)]);
94
+ // Core state management - start empty for plain mode
95
+ const [allImageIds, setAllImageIds] = useState([]);
96
+ const [selectedIds, setSelectedIds] = useState([]);
97
+ const [history, setHistory] = useState([createEmptyBatchState()]);
99
98
  const [currentIndex, setCurrentIndex] = useState(0);
100
- const [currentBatch, setCurrentBatch] = useState(() => createInitialBatch(stableImageIds));
99
+ const [currentBatch, setCurrentBatch] = useState(createEmptyBatchState());
101
100
  // Configuration refs
102
101
  const maxSizeRef = useRef(internalOptions.maxSize);
103
102
  const devWarningsRef = useRef(internalOptions.devWarnings);
@@ -105,26 +104,6 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
105
104
  useEffect(() => {
106
105
  setCurrentBatch(history[currentIndex]);
107
106
  }, [history, currentIndex]);
108
- // Update when stableImageIds actually changes (content-wise)
109
- useEffect(() => {
110
- // Only update if the actual content is different, not just reference
111
- const currentIdString = allImageIds.join(',');
112
- const newIdString = stableImageIds.join(',');
113
- if (currentIdString !== newIdString) {
114
- const newBatch = createInitialBatch(stableImageIds);
115
- setAllImageIds([...stableImageIds]); // Create new array to avoid reference issues
116
- setSelectedIds([...stableImageIds]); // Default select all new images
117
- setHistory([newBatch]);
118
- setCurrentIndex(0);
119
- setCurrentBatch(newBatch);
120
- if (internalOptions.devWarnings) {
121
- console.log('useAdjustmentHistoryBatch: ImageIds updated', {
122
- from: allImageIds,
123
- to: stableImageIds
124
- });
125
- }
126
- }
127
- }, [stableImageIds, allImageIds, createInitialBatch, internalOptions.devWarnings]);
128
107
  // Memory usage calculation
129
108
  const getMemoryUsage = useCallback(() => {
130
109
  try {
@@ -182,39 +161,44 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
182
161
  const adjustSelected = useCallback((delta) => {
183
162
  if (selectedIds.length === 0)
184
163
  return;
185
- const newBatch = { ...currentBatch };
164
+ const newBatch = {
165
+ currentSelection: { ...currentBatch.currentSelection },
166
+ allImages: { ...currentBatch.allImages },
167
+ initialStates: { ...currentBatch.initialStates }
168
+ };
169
+ // Apply adjustments to selected images in both currentSelection and allImages
186
170
  for (const imageId of selectedIds) {
187
- if (newBatch[imageId]) {
188
- newBatch[imageId] = {
189
- ...newBatch[imageId],
171
+ if (newBatch.currentSelection[imageId]) {
172
+ // Update current selection
173
+ newBatch.currentSelection[imageId] = {
174
+ ...newBatch.currentSelection[imageId],
190
175
  ...Object.fromEntries(Object.entries(delta).map(([key, value]) => [
191
176
  key,
192
- newBatch[imageId][key] + value
177
+ newBatch.currentSelection[imageId][key] + value
193
178
  ]))
194
179
  };
180
+ // Also update in allImages to persist the changes
181
+ newBatch.allImages[imageId] = { ...newBatch.currentSelection[imageId] };
195
182
  }
196
183
  }
197
184
  pushBatchState(newBatch);
198
185
  }, [selectedIds, currentBatch, pushBatchState]);
199
- // Set specific adjustment states for specified images
200
- const setAdjustments = useCallback((adjustments) => {
201
- const newBatch = { ...currentBatch };
202
- for (const [imageId, adjustment] of Object.entries(adjustments)) {
203
- if (newBatch[imageId]) {
204
- newBatch[imageId] = { ...newBatch[imageId], ...adjustment };
205
- }
206
- }
207
- pushBatchState(newBatch);
208
- }, [currentBatch, pushBatchState]);
186
+ // Set specific adjustment states for specified images (removed since not needed)
209
187
  // Undo last changes to selected images
210
188
  const undo = useCallback(() => {
211
189
  if (currentIndex > 0 && selectedIds.length > 0) {
212
190
  const previousBatch = history[currentIndex - 1];
213
- const newBatch = { ...currentBatch };
214
- // Only restore selected images from previous state
191
+ const newBatch = {
192
+ currentSelection: { ...currentBatch.currentSelection },
193
+ allImages: { ...currentBatch.allImages },
194
+ initialStates: { ...currentBatch.initialStates }
195
+ };
196
+ // Only restore adjustments for currently selected images
215
197
  for (const imageId of selectedIds) {
216
- if (previousBatch[imageId] && newBatch[imageId]) {
217
- newBatch[imageId] = { ...previousBatch[imageId] };
198
+ if (previousBatch.allImages[imageId] && newBatch.currentSelection[imageId]) {
199
+ // Restore from previous allImages state (not currentSelection)
200
+ newBatch.currentSelection[imageId] = { ...previousBatch.allImages[imageId] };
201
+ newBatch.allImages[imageId] = { ...previousBatch.allImages[imageId] };
218
202
  }
219
203
  }
220
204
  // Update current batch and move index back
@@ -226,17 +210,29 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
226
210
  newHistory[currentIndex] = newBatch;
227
211
  return newHistory;
228
212
  });
213
+ if (internalOptions.devWarnings) {
214
+ console.log('useAdjustmentHistoryBatch: Undo completed for selected images only', {
215
+ selectedImages: selectedIds,
216
+ currentIndex: currentIndex - 1
217
+ });
218
+ }
229
219
  }
230
- }, [currentIndex, selectedIds, history, currentBatch]);
220
+ }, [currentIndex, selectedIds, history, currentBatch, internalOptions.devWarnings]);
231
221
  // Redo next changes to selected images
232
222
  const redo = useCallback(() => {
233
223
  if (currentIndex < history.length - 1 && selectedIds.length > 0) {
234
224
  const nextBatch = history[currentIndex + 1];
235
- const newBatch = { ...currentBatch };
236
- // Only restore selected images from next state
225
+ const newBatch = {
226
+ currentSelection: { ...currentBatch.currentSelection },
227
+ allImages: { ...currentBatch.allImages },
228
+ initialStates: { ...currentBatch.initialStates }
229
+ };
230
+ // Only restore adjustments for currently selected images
237
231
  for (const imageId of selectedIds) {
238
- if (nextBatch[imageId] && newBatch[imageId]) {
239
- newBatch[imageId] = { ...nextBatch[imageId] };
232
+ if (nextBatch.allImages[imageId] && newBatch.currentSelection[imageId]) {
233
+ // Restore from next allImages state (not currentSelection)
234
+ newBatch.currentSelection[imageId] = { ...nextBatch.allImages[imageId] };
235
+ newBatch.allImages[imageId] = { ...nextBatch.allImages[imageId] };
240
236
  }
241
237
  }
242
238
  // Update current batch and move index forward
@@ -248,48 +244,143 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
248
244
  newHistory[currentIndex + 1] = newBatch;
249
245
  return newHistory;
250
246
  });
247
+ if (internalOptions.devWarnings) {
248
+ console.log('useAdjustmentHistoryBatch: Redo completed for selected images only', {
249
+ selectedImages: selectedIds,
250
+ currentIndex: currentIndex + 1
251
+ });
252
+ }
251
253
  }
252
- }, [currentIndex, selectedIds, history, currentBatch]);
254
+ }, [currentIndex, selectedIds, history, currentBatch, internalOptions.devWarnings]);
253
255
  // Reset selected images to default state
254
256
  const reset = useCallback((imageIds) => {
255
257
  const idsToReset = imageIds || selectedIds;
256
258
  if (idsToReset.length === 0)
257
259
  return;
258
- const newBatch = { ...currentBatch };
260
+ const newBatch = {
261
+ currentSelection: { ...currentBatch.currentSelection },
262
+ allImages: { ...currentBatch.allImages },
263
+ initialStates: { ...currentBatch.initialStates }
264
+ };
259
265
  const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
260
266
  for (const imageId of idsToReset) {
261
- if (newBatch[imageId]) {
262
- newBatch[imageId] = { ...defaultState };
267
+ if (newBatch.currentSelection[imageId]) {
268
+ newBatch.currentSelection[imageId] = { ...defaultState };
269
+ newBatch.allImages[imageId] = { ...defaultState };
263
270
  }
264
271
  }
265
272
  pushBatchState(newBatch);
266
273
  }, [selectedIds, currentBatch, pushBatchState, internalOptions.defaultAdjustmentState]);
267
- // Selection management
268
- const setSelection = useCallback((imageIds) => {
269
- // Validate that all imageIds exist
270
- const validIds = imageIds.filter(id => allImageIds.includes(id));
271
- setSelectedIds(validIds);
272
- if (devWarningsRef.current && validIds.length !== imageIds.length) {
273
- console.warn('Some image IDs in setSelection do not exist:', imageIds.filter(id => !allImageIds.includes(id)));
274
- }
275
- }, [allImageIds]);
276
- const toggleSelection = useCallback((imageId) => {
277
- if (!allImageIds.includes(imageId)) {
278
- if (devWarningsRef.current) {
279
- console.warn('toggleSelection: Image ID does not exist:', imageId);
274
+ // Selection management with initial adjustments - single state update
275
+ const setSelection = useCallback((configs) => {
276
+ const imageIds = configs.map(config => config.imageId);
277
+ // Update selectedIds state
278
+ setSelectedIds(imageIds);
279
+ // Build new batch state with initial adjustments
280
+ const newBatch = {
281
+ currentSelection: {},
282
+ allImages: { ...currentBatch.allImages },
283
+ initialStates: { ...currentBatch.initialStates }
284
+ };
285
+ // Process each image config
286
+ for (const config of configs) {
287
+ const { imageId, adjustment } = config;
288
+ // If image exists in allImages, use its state
289
+ if (currentBatch.allImages[imageId]) {
290
+ newBatch.currentSelection[imageId] = { ...currentBatch.allImages[imageId] };
291
+ }
292
+ else {
293
+ // New image - determine initial state
294
+ let initialState;
295
+ if (adjustment) {
296
+ // Use provided adjustment as initial state
297
+ initialState = {
298
+ ...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
299
+ ...adjustment
300
+ };
301
+ }
302
+ else {
303
+ // Use default state
304
+ initialState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
305
+ }
306
+ // Set initial state for new image
307
+ newBatch.currentSelection[imageId] = { ...initialState };
308
+ newBatch.allImages[imageId] = { ...initialState };
309
+ newBatch.initialStates[imageId] = { ...initialState };
280
310
  }
311
+ }
312
+ // Update allImageIds to include any new images
313
+ const newAllImageIds = Array.from(new Set([...allImageIds, ...imageIds]));
314
+ setAllImageIds(newAllImageIds);
315
+ // Single state update to prevent multiple re-renders
316
+ setCurrentBatch(newBatch);
317
+ if (internalOptions.devWarnings) {
318
+ console.log('useAdjustmentHistoryBatch: Selection updated with initial adjustments', {
319
+ selected: imageIds,
320
+ totalImages: newAllImageIds.length,
321
+ newImages: imageIds.filter(id => !allImageIds.includes(id)),
322
+ withInitialAdjustments: configs.filter(c => c.adjustment).length
323
+ });
324
+ }
325
+ }, [allImageIds, currentBatch, internalOptions.defaultAdjustmentState, internalOptions.devWarnings]);
326
+ // Sync adjustments for specific images (clears their history) - single state update
327
+ const syncAdjustment = useCallback((configs) => {
328
+ if (configs.length === 0)
281
329
  return;
330
+ // Build new batch state
331
+ const newBatch = {
332
+ currentSelection: { ...currentBatch.currentSelection },
333
+ allImages: { ...currentBatch.allImages },
334
+ initialStates: { ...currentBatch.initialStates }
335
+ };
336
+ // Process each sync config
337
+ for (const config of configs) {
338
+ const { imageId, adjustment } = config;
339
+ if (adjustment) {
340
+ const fullAdjustment = {
341
+ ...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
342
+ ...adjustment
343
+ };
344
+ // Update all states for this image
345
+ newBatch.allImages[imageId] = { ...fullAdjustment };
346
+ newBatch.initialStates[imageId] = { ...fullAdjustment };
347
+ // If image is currently selected, update current selection too
348
+ if (newBatch.currentSelection[imageId]) {
349
+ newBatch.currentSelection[imageId] = { ...fullAdjustment };
350
+ }
351
+ }
352
+ }
353
+ // Clear history and start fresh with synced state
354
+ const freshHistory = [newBatch];
355
+ setHistory(freshHistory);
356
+ setCurrentIndex(0);
357
+ setCurrentBatch(newBatch);
358
+ if (internalOptions.devWarnings) {
359
+ const syncedImageIds = configs.map(c => c.imageId);
360
+ console.log('useAdjustmentHistoryBatch: Synced adjustments (history cleared)', {
361
+ syncedImages: syncedImageIds,
362
+ historyCleared: true
363
+ });
282
364
  }
365
+ }, [currentBatch, internalOptions.defaultAdjustmentState, internalOptions.devWarnings]);
366
+ const toggleSelection = useCallback((imageId) => {
283
367
  setSelectedIds(prev => prev.includes(imageId)
284
368
  ? prev.filter(id => id !== imageId)
285
369
  : [...prev, imageId]);
286
- }, [allImageIds]);
370
+ }, []);
287
371
  const selectAll = useCallback(() => {
288
372
  setSelectedIds([...allImageIds]);
289
373
  }, [allImageIds]);
290
374
  const clearSelection = useCallback(() => {
291
375
  setSelectedIds([]);
292
- }, []);
376
+ // Clear currentSelection but keep allImages and initialStates
377
+ const newBatch = {
378
+ currentSelection: {},
379
+ allImages: { ...currentBatch.allImages },
380
+ initialStates: { ...currentBatch.initialStates }
381
+ };
382
+ setCurrentBatch(newBatch);
383
+ }, [currentBatch]);
293
384
  // Other history actions
294
385
  const jumpToIndex = useCallback((index) => {
295
386
  if (index >= 0 && index < history.length) {
@@ -298,45 +389,49 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
298
389
  }
299
390
  }, [history]);
300
391
  const clearHistory = useCallback(() => {
301
- const freshBatch = createInitialBatch(allImageIds);
392
+ const freshBatch = createEmptyBatchState();
302
393
  setHistory([freshBatch]);
303
394
  setCurrentIndex(0);
304
395
  setCurrentBatch(freshBatch);
305
- }, [allImageIds, createInitialBatch]);
396
+ setAllImageIds([]);
397
+ setSelectedIds([]);
398
+ }, []);
306
399
  const getCurrentBatch = useCallback(() => {
307
- return { ...currentBatch };
400
+ return {
401
+ currentSelection: { ...currentBatch.currentSelection },
402
+ allImages: { ...currentBatch.allImages },
403
+ initialStates: { ...currentBatch.initialStates }
404
+ };
308
405
  }, [currentBatch]);
309
406
  const syncBatch = useCallback((newBatch, targetIndex) => {
310
407
  // Validate input
311
- if (!newBatch || typeof newBatch !== 'object') {
312
- console.warn('syncBatch: newBatch must be a valid BatchAdjustmentState object');
313
- return;
314
- }
315
- // Validate all items are AdjustmentState objects
316
- const isValidBatch = Object.values(newBatch).every(state => state && typeof state === 'object' &&
317
- typeof state.tempScore === 'number' &&
318
- typeof state.tintScore === 'number' &&
319
- typeof state.vibranceScore === 'number' &&
320
- typeof state.saturationScore === 'number' &&
321
- typeof state.exposureScore === 'number' &&
322
- typeof state.highlightsScore === 'number' &&
323
- typeof state.shadowsScore === 'number' &&
324
- typeof state.whitesScore === 'number' &&
325
- typeof state.blacksScore === 'number' &&
326
- typeof state.contrastScore === 'number' &&
327
- typeof state.clarityScore === 'number' &&
328
- typeof state.sharpnessScore === 'number');
329
- if (!isValidBatch) {
330
- console.warn('syncBatch: All values in newBatch must be valid AdjustmentState objects');
408
+ if (!newBatch || typeof newBatch !== 'object' || !newBatch.currentSelection || !newBatch.allImages || !newBatch.initialStates) {
409
+ console.warn('syncBatch: newBatch must be a valid BatchAdjustmentState object with currentSelection, allImages, and initialStates');
331
410
  return;
332
411
  }
333
412
  // Update current state
334
- setCurrentBatch({ ...newBatch });
413
+ setCurrentBatch({
414
+ currentSelection: { ...newBatch.currentSelection },
415
+ allImages: { ...newBatch.allImages },
416
+ initialStates: { ...newBatch.initialStates }
417
+ });
335
418
  // Replace history with single entry
336
- setHistory([{ ...newBatch }]);
419
+ setHistory([{
420
+ currentSelection: { ...newBatch.currentSelection },
421
+ allImages: { ...newBatch.allImages },
422
+ initialStates: { ...newBatch.initialStates }
423
+ }]);
337
424
  setCurrentIndex(0);
425
+ // Update image tracking
426
+ const allImageIds = Object.keys(newBatch.allImages);
427
+ const selectedImageIds = Object.keys(newBatch.currentSelection);
428
+ setAllImageIds(allImageIds);
429
+ setSelectedIds(selectedImageIds);
338
430
  if (devWarningsRef.current) {
339
- console.log('syncBatch: Synchronized batch state with', Object.keys(newBatch).length, 'images');
431
+ console.log('syncBatch: Synchronized batch state', {
432
+ totalImages: allImageIds.length,
433
+ selectedImages: selectedImageIds.length
434
+ });
340
435
  }
341
436
  }, []);
342
437
  // Configuration actions
@@ -346,52 +441,6 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
346
441
  enforceMaxSize();
347
442
  }
348
443
  }, [enforceMaxSize]);
349
- const addImages = useCallback((imageIds, selectNew = true) => {
350
- const newIds = imageIds.filter(id => !allImageIds.includes(id));
351
- if (newIds.length === 0)
352
- return;
353
- const updatedAllIds = [...allImageIds, ...newIds];
354
- const newBatch = { ...currentBatch };
355
- // Add default states for new images
356
- for (const id of newIds) {
357
- newBatch[id] = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
358
- }
359
- setAllImageIds(updatedAllIds);
360
- setCurrentBatch(newBatch);
361
- // Update history
362
- setHistory(prevHistory => {
363
- const newHistory = [...prevHistory];
364
- newHistory[currentIndex] = newBatch;
365
- return newHistory;
366
- });
367
- // Optionally select new images
368
- if (selectNew) {
369
- setSelectedIds(prev => [...prev, ...newIds]);
370
- }
371
- }, [allImageIds, currentBatch, currentIndex, internalOptions.defaultAdjustmentState]);
372
- const removeImages = useCallback((imageIds) => {
373
- const updatedAllIds = allImageIds.filter(id => !imageIds.includes(id));
374
- const updatedSelectedIds = selectedIds.filter(id => !imageIds.includes(id));
375
- const newBatch = { ...currentBatch };
376
- // Remove images from batch
377
- for (const id of imageIds) {
378
- delete newBatch[id];
379
- }
380
- setAllImageIds(updatedAllIds);
381
- setSelectedIds(updatedSelectedIds);
382
- setCurrentBatch(newBatch);
383
- // Update history
384
- setHistory(prevHistory => {
385
- const newHistory = prevHistory.map(batch => {
386
- const cleanedBatch = { ...batch };
387
- for (const id of imageIds) {
388
- delete cleanedBatch[id];
389
- }
390
- return cleanedBatch;
391
- });
392
- return newHistory;
393
- });
394
- }, [allImageIds, selectedIds, currentBatch]);
395
444
  // History info object
396
445
  const historyInfo = useMemo(() => ({
397
446
  canUndo: currentIndex > 0 && selectedIds.length > 0,
@@ -405,11 +454,11 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
405
454
  // Actions object - stabilized with useMemo
406
455
  const actions = useMemo(() => ({
407
456
  adjustSelected,
408
- setAdjustments,
409
457
  undo,
410
458
  redo,
411
459
  reset,
412
460
  setSelection,
461
+ syncAdjustment,
413
462
  toggleSelection,
414
463
  selectAll,
415
464
  clearSelection,
@@ -418,17 +467,15 @@ export function useAdjustmentHistoryBatch(imageIds, options = {}) {
418
467
  getCurrentBatch,
419
468
  syncBatch
420
469
  }), [
421
- adjustSelected, setAdjustments, undo, redo, reset,
422
- setSelection, toggleSelection, selectAll, clearSelection,
470
+ adjustSelected, undo, redo, reset,
471
+ setSelection, syncAdjustment, toggleSelection, selectAll, clearSelection,
423
472
  jumpToIndex, clearHistory, getCurrentBatch, syncBatch
424
473
  ]);
425
474
  // Config object - stabilized with useMemo
426
475
  const config = useMemo(() => ({
427
476
  setMaxSize,
428
- getMemoryUsage,
429
- addImages,
430
- removeImages
431
- }), [setMaxSize, getMemoryUsage, addImages, removeImages]);
477
+ getMemoryUsage
478
+ }), [setMaxSize, getMemoryUsage]);
432
479
  // Apply max size enforcement when history changes
433
480
  useEffect(() => {
434
481
  enforceMaxSize();
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@ export { default as HPresetOptionsMenu } from './components/editor/HPresetOption
21
21
  export { HAlertInternetBox, HAlertCopyBox, HAlertInternetConnectionBox, HAlertPresetSave } from './components/editor/HAlertBox';
22
22
  export { useHonchoEditorBulk } from './hooks/editor/useHonchoEditorBulk';
23
23
  export { useAdjustmentHistory, type UseAdjustmentHistoryReturn, type HistoryInfo, type HistoryActions, type HistoryConfig } from './hooks/useAdjustmentHistory';
24
- export { useAdjustmentHistoryBatch, type UseAdjustmentHistoryBatchReturn, type BatchAdjustmentState, type BatchHistoryInfo, type BatchHistoryActions, type BatchHistoryConfig } from './hooks/useAdjustmentHistoryBatch';
24
+ export { useAdjustmentHistoryBatch, type UseAdjustmentHistoryBatchReturn, type BatchAdjustmentState, type ImageAdjustmentConfig, type BatchHistoryInfo, type BatchHistoryActions, type BatchHistoryConfig } from './hooks/useAdjustmentHistoryBatch';
25
25
  export { default as useColors } from './themes/colors';
26
26
  export { default as useHonchoTypography } from './themes/honchoTheme';
27
27
  export { default as useIsMobile } from './utils/isMobile';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yogiswara/honcho-editor-ui",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "A complete UI component library for the Honcho photo editor.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -35,6 +35,7 @@
35
35
  "@types/node": "^20",
36
36
  "@types/react": "^18",
37
37
  "@types/react-dom": "^18",
38
+ "@types/react-responsive-masonry": "^2.6.0",
38
39
  "jest": "^30.0.5",
39
40
  "jest-environment-jsdom": "^30.0.5",
40
41
  "next": "^13.5.6 || ^14.0.0",
@@ -46,5 +47,8 @@
46
47
  },
47
48
  "license": "ISC",
48
49
  "keywords": [],
49
- "author": ""
50
+ "author": "",
51
+ "dependencies": {
52
+ "react-responsive-masonry": "^2.7.1"
53
+ }
50
54
  }