@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.
- package/dist/components/editor/GalleryAlbum/AlbumImageGallery.d.ts +0 -0
- package/dist/components/editor/GalleryAlbum/AlbumImageGallery.js +52 -0
- package/dist/components/editor/GalleryAlbum/ImageItem.d.ts +0 -0
- package/dist/components/editor/GalleryAlbum/ImageItem.js +246 -0
- package/dist/components/editor/HSliderLightMobile.js +1 -1
- package/dist/hooks/editor/type.d.ts +12 -0
- package/dist/hooks/editor/useHonchoEditor.js +1 -28
- package/dist/hooks/editor/useHonchoEditorBulk.d.ts +13 -2
- package/dist/hooks/editor/useHonchoEditorBulk.js +12 -1
- package/dist/hooks/useAdjustmentHistoryBatch.d.ts +61 -17
- package/dist/hooks/useAdjustmentHistoryBatch.js +245 -198
- package/dist/index.d.ts +1 -1
- package/package.json +6 -2
|
File without changes
|
|
@@ -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;
|
|
File without changes
|
|
@@ -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:
|
|
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();
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
53
|
-
setSelection: (
|
|
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
|
-
*
|
|
103
|
-
* -
|
|
104
|
-
* -
|
|
105
|
-
* -
|
|
106
|
-
* -
|
|
107
|
-
*
|
|
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(
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
*
|
|
61
|
-
* -
|
|
62
|
-
* -
|
|
63
|
-
* -
|
|
64
|
-
* -
|
|
65
|
-
*
|
|
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(
|
|
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
|
-
//
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
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(()
|
|
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 = {
|
|
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
|
-
|
|
189
|
-
|
|
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 = {
|
|
214
|
-
|
|
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
|
-
|
|
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 = {
|
|
236
|
-
|
|
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
|
-
|
|
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 = {
|
|
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((
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
setSelectedIds(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
}, [
|
|
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 =
|
|
392
|
+
const freshBatch = createEmptyBatchState();
|
|
302
393
|
setHistory([freshBatch]);
|
|
303
394
|
setCurrentIndex(0);
|
|
304
395
|
setCurrentBatch(freshBatch);
|
|
305
|
-
|
|
396
|
+
setAllImageIds([]);
|
|
397
|
+
setSelectedIds([]);
|
|
398
|
+
}, []);
|
|
306
399
|
const getCurrentBatch = useCallback(() => {
|
|
307
|
-
return {
|
|
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({
|
|
413
|
+
setCurrentBatch({
|
|
414
|
+
currentSelection: { ...newBatch.currentSelection },
|
|
415
|
+
allImages: { ...newBatch.allImages },
|
|
416
|
+
initialStates: { ...newBatch.initialStates }
|
|
417
|
+
});
|
|
335
418
|
// Replace history with single entry
|
|
336
|
-
setHistory([{
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
}
|