@yogiswara/honcho-editor-ui 2.5.8 → 2.5.10

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.
@@ -2,8 +2,6 @@ interface Props {
2
2
  anchorEl: null | HTMLElement;
3
3
  isOpen: boolean;
4
4
  onClose: () => void;
5
- isPresetSelected?: boolean;
6
- onRemove: () => void;
7
5
  onRename: () => void;
8
6
  onDelete: () => void;
9
7
  }
@@ -16,5 +16,5 @@ export default function HPresetOptionsMenu(props) {
16
16
  },
17
17
  },
18
18
  },
19
- }, children: [props.isPresetSelected && (_jsx(MenuItem, { onClick: props.onRemove, children: _jsxs(Stack, { direction: "row", spacing: "10px", children: [_jsx(CardMedia, { component: "img", image: "/v1/svg/remove-preset-menu-button.svg", sx: { width: '20px', height: '20px' } }), _jsx(ListItemText, { sx: { ...typography.bodyMedium }, children: "Remove Preset" })] }) })), _jsx(MenuItem, { onClick: props.onRename, children: _jsxs(Stack, { direction: "row", spacing: "10px", children: [_jsx(CardMedia, { component: "img", image: "/v1/svg/rename-menu-button.svg", sx: { width: '20px', height: '20px' } }), _jsx(ListItemText, { sx: { ...typography.bodyMedium }, children: "Rename" })] }) }), _jsx(MenuItem, { onClick: props.onDelete, children: _jsxs(Stack, { direction: "row", spacing: "10px", children: [_jsx(CardMedia, { component: "img", image: "/v1/svg/delete-menu-button.svg", sx: { width: '20px', height: '20px' } }), _jsx(ListItemText, { sx: { ...typography.bodyMedium, color: colors.error }, children: "Delete" })] }) })] }));
19
+ }, children: [_jsx(MenuItem, { onClick: props.onRename, children: _jsxs(Stack, { direction: "row", spacing: "10px", children: [_jsx(CardMedia, { component: "img", image: "/v1/svg/rename-menu-button.svg", sx: { width: '20px', height: '20px' } }), _jsx(ListItemText, { sx: { ...typography.bodyMedium }, children: "Rename" })] }) }), _jsx(MenuItem, { onClick: props.onDelete, children: _jsxs(Stack, { direction: "row", spacing: "10px", children: [_jsx(CardMedia, { component: "img", image: "/v1/svg/delete-menu-button.svg", sx: { width: '20px', height: '20px' } }), _jsx(ListItemText, { sx: { ...typography.bodyMedium, color: colors.error }, children: "Delete" })] }) })] }));
20
20
  }
@@ -30,6 +30,8 @@ export declare function useHonchoEditorBulk(controller: Controller, eventID: str
30
30
  handleBackCallbackBulk: () => void;
31
31
  selectedBulkPreset: string;
32
32
  handleToggleImageSelection: (imageId: string) => void;
33
+ handleLoadMore: () => Promise<void>;
34
+ handleRefresh: () => Promise<void>;
33
35
  handleSelectBulkPreset: (event: SelectChangeEvent<string>) => void;
34
36
  handleBulkTempDecreaseMax: () => void;
35
37
  handleBulkTempDecrease: () => void;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
- import { useState, useCallback, useEffect, useMemo } from 'react';
2
+ import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
3
3
  import { useAdjustmentHistoryBatch } from '../useAdjustmentHistoryBatch';
4
+ import { usePaging } from "../usePaging";
4
5
  // Helper function to map the API response to the format our UI component needs
5
6
  const mapGalleryToPhotoData = (gallery, selectedIds) => {
6
7
  return {
@@ -40,24 +41,36 @@ function mapColorAdjustmentToAdjustmentState(adj) {
40
41
  }
41
42
  export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
42
43
  const { currentBatch, selectedIds, actions: batchActions } = useAdjustmentHistoryBatch();
43
- // State for Bulk Editing
44
- const [imageCollection, setImageCollection] = useState([]);
45
- const [isLoading, setIsLoading] = useState(true);
46
- const [error, setError] = useState(null);
47
- const [page, setPage] = useState(1);
48
- const [isFetchingMore, setIsFetchingMore] = useState(false);
49
- const [hasMore, setHasMore] = useState(true);
44
+ const { images, info, actions } = usePaging(controller, firebaseUid, eventID);
45
+ // Track which images have been synced to prevent loops
46
+ const lastSyncedImageIds = useRef(new Set());
47
+ // State for Bulk Editing
50
48
  const [selectedBulkPreset, setSelectedBulkPreset] = useState('preset1');
49
+ // Use loading states from usePaging instead of local state
50
+ const isLoading = info.isLoading;
51
+ const error = info.error;
52
+ const hasMore = info.hasMore;
51
53
  const imageData = useMemo(() => {
52
- return imageCollection.map(item => {
53
- console.log("item FROM USEHONCHOBULK: ", item);
54
- return mapGalleryToPhotoData(item, selectedIds);
55
- }).map(item => {
56
- const adjustment = currentBatch.allImages[item.key];
57
- console.log("adjustment FROM USEHONCHOBULK: ", adjustment);
58
- return adjustment ? { ...item, ...adjustment } : item;
54
+ return images.map(item => {
55
+ const basePhoto = mapGalleryToPhotoData(item, selectedIds);
56
+ const batchAdjustment = currentBatch.allImages[item.id];
57
+ // Merge batch adjustments over backend adjustments
58
+ return batchAdjustment ? { ...basePhoto, ...batchAdjustment } : basePhoto;
59
59
  });
60
- }, [imageCollection, selectedIds, currentBatch.allImages]);
60
+ }, [images, selectedIds, currentBatch.allImages]);
61
+ // Safe sync: Only sync new images to prevent infinite loops
62
+ useEffect(() => {
63
+ if (images.length === 0)
64
+ return;
65
+ // Check if we have new images that haven't been synced
66
+ const currentImageIds = new Set(images.map(img => img.id));
67
+ const hasNewImages = images.some(img => !lastSyncedImageIds.current.has(img.id));
68
+ if (hasNewImages) {
69
+ console.log('[useHonchoEditorBulk] Syncing new images to batch:', images.length);
70
+ batchActions.syncAdjustment(images.map(mapToImageAdjustmentConfig));
71
+ lastSyncedImageIds.current = currentImageIds;
72
+ }
73
+ }, [images, batchActions]);
61
74
  const handleBackCallbackBulk = useCallback(() => {
62
75
  const lastSelectedId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : "";
63
76
  controller.handleBack(firebaseUid, lastSelectedId);
@@ -148,26 +161,8 @@ export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
148
161
  // loadImages(page + 1);
149
162
  // }
150
163
  // }, [isFetchingMore, hasMore, page, loadImages]);
151
- useEffect(() => {
152
- if (eventID && firebaseUid) {
153
- setIsLoading(true);
154
- setError(null);
155
- controller.getImageList(firebaseUid, eventID, 2)
156
- .then(response => {
157
- // TODO need do pagination for this one
158
- batchActions.syncAdjustment(response.gallery.map(mapToImageAdjustmentConfig));
159
- setImageCollection(response.gallery);
160
- })
161
- .catch(err => {
162
- console.error("Failed to fetch image list:", err);
163
- setError("Could not load images.");
164
- })
165
- .finally(() => {
166
- setIsLoading(false);
167
- });
168
- console.log("Image data FROM USEHONCHOBULK: ", imageData);
169
- }
170
- }, [eventID, firebaseUid, controller]);
164
+ // Note: Image loading is now handled by usePaging hook
165
+ // The sync logic above handles syncing loaded images to batch state
171
166
  // useEffect(() => {
172
167
  // if (eventID && firebaseUid) {
173
168
  // setImageCollection([]); // reset when event changes
@@ -186,6 +181,8 @@ export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
186
181
  handleBackCallbackBulk,
187
182
  selectedBulkPreset,
188
183
  handleToggleImageSelection: batchActions.toggleSelection,
184
+ handleLoadMore: actions.loadMore,
185
+ handleRefresh: actions.refresh,
189
186
  handleSelectBulkPreset,
190
187
  // Adjustment
191
188
  handleBulkTempDecreaseMax,
@@ -0,0 +1,89 @@
1
+ import { Gallery } from './editor/type';
2
+ import { Controller } from "./editor/useHonchoEditor";
3
+ /**
4
+ * Configuration options for the paging hook
5
+ */
6
+ export interface PagingOptions {
7
+ /** Enable development warnings for debugging */
8
+ devWarnings?: boolean;
9
+ /** Auto-load first page on hook initialization */
10
+ autoLoad?: boolean;
11
+ /** Reset pagination when dependencies change */
12
+ autoReset?: boolean;
13
+ }
14
+ /**
15
+ * Information about the current paging state
16
+ */
17
+ export interface PagingInfo {
18
+ /** Whether images are currently being loaded */
19
+ isLoading: boolean;
20
+ /** Whether more pages are being loaded */
21
+ isLoadingMore: boolean;
22
+ /** Error message if any operation failed */
23
+ error: string | null;
24
+ /** Current page number */
25
+ currentPage: number;
26
+ /** Whether there are more pages to load */
27
+ hasMore: boolean;
28
+ /** Total number of images loaded */
29
+ totalImages: number;
30
+ /** Whether the hook has been initialized */
31
+ isInitialized: boolean;
32
+ }
33
+ /**
34
+ * Actions available for paging management
35
+ */
36
+ export interface PagingActions {
37
+ /** Load more images (next page) - only one instance can run at a time */
38
+ loadMore: () => Promise<void>;
39
+ /** Refresh/reload from first page */
40
+ refresh: () => Promise<void>;
41
+ /** Reset pagination state */
42
+ reset: () => void;
43
+ }
44
+ /**
45
+ * Return type for the usePaging hook
46
+ */
47
+ export interface UsePagingReturn {
48
+ /** Current list of images */
49
+ images: Gallery[];
50
+ /** Information about paging state */
51
+ info: PagingInfo;
52
+ /** Available paging actions */
53
+ actions: PagingActions;
54
+ }
55
+ /**
56
+ * Hook for managing paginated image loading with ControllerBulk.
57
+ *
58
+ * **Key Features:**
59
+ * - **Paginated Loading**: Handles page-by-page image loading
60
+ * - **State Management**: Maintains image list and pagination state
61
+ * - **Load More**: Seamlessly loads and appends next pages
62
+ * - **Mutation**: Update specific images without full reload
63
+ * - **Error Handling**: Provides error states for failed operations
64
+ * - **Auto-loading**: Optional automatic first page loading
65
+ *
66
+ * **Typical Usage:**
67
+ * ```typescript
68
+ * const { images, info, actions } = usePaging(controller, firebaseUid, eventId, {
69
+ * autoLoad: true,
70
+ * autoReset: true
71
+ * });
72
+ *
73
+ * // Load more images
74
+ * await actions.loadMore();
75
+ *
76
+ * // Refresh from beginning
77
+ * await actions.refresh();
78
+ *
79
+ * // Update specific image
80
+ * actions.mutateImage(imageId, (img) => ({ ...img, isSelected: true }));
81
+ * ```
82
+ *
83
+ * @param controller - Backend controller for API communication
84
+ * @param firebaseUid - User identifier for backend operations
85
+ * @param eventId - Event identifier for image list
86
+ * @param options - Configuration options
87
+ * @returns Object with images, info, and actions
88
+ */
89
+ export declare function usePaging(controller: Controller | null, firebaseUid: string, eventId: string, options?: PagingOptions): UsePagingReturn;
@@ -0,0 +1,211 @@
1
+ import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
2
+ /**
3
+ * Hook for managing paginated image loading with ControllerBulk.
4
+ *
5
+ * **Key Features:**
6
+ * - **Paginated Loading**: Handles page-by-page image loading
7
+ * - **State Management**: Maintains image list and pagination state
8
+ * - **Load More**: Seamlessly loads and appends next pages
9
+ * - **Mutation**: Update specific images without full reload
10
+ * - **Error Handling**: Provides error states for failed operations
11
+ * - **Auto-loading**: Optional automatic first page loading
12
+ *
13
+ * **Typical Usage:**
14
+ * ```typescript
15
+ * const { images, info, actions } = usePaging(controller, firebaseUid, eventId, {
16
+ * autoLoad: true,
17
+ * autoReset: true
18
+ * });
19
+ *
20
+ * // Load more images
21
+ * await actions.loadMore();
22
+ *
23
+ * // Refresh from beginning
24
+ * await actions.refresh();
25
+ *
26
+ * // Update specific image
27
+ * actions.mutateImage(imageId, (img) => ({ ...img, isSelected: true }));
28
+ * ```
29
+ *
30
+ * @param controller - Backend controller for API communication
31
+ * @param firebaseUid - User identifier for backend operations
32
+ * @param eventId - Event identifier for image list
33
+ * @param options - Configuration options
34
+ * @returns Object with images, info, and actions
35
+ */
36
+ export function usePaging(controller, firebaseUid, eventId, options = {}) {
37
+ // Memoize options to prevent re-renders when object is recreated with same values
38
+ const memoizedOptions = useMemo(() => ({
39
+ devWarnings: options.devWarnings ?? false,
40
+ autoLoad: options.autoLoad ?? false,
41
+ autoReset: options.autoReset ?? true
42
+ }), [options.devWarnings, options.autoLoad, options.autoReset]);
43
+ // Core state
44
+ const [images, setImages] = useState([]);
45
+ const [isLoading, setIsLoading] = useState(false);
46
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
47
+ const [error, setError] = useState(null);
48
+ const [currentPage, setCurrentPage] = useState(1);
49
+ const [hasMore, setHasMore] = useState(true);
50
+ const [isInitialized, setIsInitialized] = useState(false);
51
+ // Track if loadMore is currently running to prevent spam
52
+ const isLoadMoreRunningRef = useRef(false);
53
+ // Track dependencies with stable refs
54
+ const controllerRef = useRef(controller);
55
+ const firebaseUidRef = useRef(firebaseUid);
56
+ const eventIdRef = useRef(eventId);
57
+ // Only update refs when values actually change
58
+ if (controllerRef.current !== controller) {
59
+ controllerRef.current = controller;
60
+ }
61
+ if (firebaseUidRef.current !== firebaseUid) {
62
+ firebaseUidRef.current = firebaseUid;
63
+ }
64
+ if (eventIdRef.current !== eventId) {
65
+ eventIdRef.current = eventId;
66
+ }
67
+ // Helper function to log debug messages
68
+ const debugLog = useCallback((message, data) => {
69
+ if (memoizedOptions.devWarnings) {
70
+ console.log(`[usePaging] ${message}`, data || '');
71
+ }
72
+ }, [memoizedOptions.devWarnings]);
73
+ // Helper function to handle errors
74
+ const handleError = useCallback((operation, error) => {
75
+ const errorMessage = `Failed to ${operation}: ${error?.message || error}`;
76
+ setError(errorMessage);
77
+ debugLog(`Error in ${operation}`, error);
78
+ }, [debugLog]);
79
+ // Load images for a specific page
80
+ const loadPage = useCallback(async (pageNum, isLoadMore = false) => {
81
+ if (!controllerRef.current || !firebaseUidRef.current || !eventIdRef.current) {
82
+ debugLog('Load skipped: missing controller, firebaseUid, or eventId');
83
+ return;
84
+ }
85
+ // Set appropriate loading state
86
+ if (isLoadMore) {
87
+ setIsLoadingMore(true);
88
+ }
89
+ else {
90
+ setIsLoading(true);
91
+ }
92
+ setError(null);
93
+ try {
94
+ debugLog(`Loading page ${pageNum}...`, { isLoadMore });
95
+ const response = await controllerRef.current.getImageList(firebaseUidRef.current, eventIdRef.current, pageNum);
96
+ debugLog('Page loaded successfully', {
97
+ page: response.current_page,
98
+ imageCount: response.gallery.length,
99
+ hasNext: response.next_page > 0
100
+ });
101
+ // Update images list
102
+ setImages(prev => {
103
+ if (isLoadMore) {
104
+ // Append new images for load more
105
+ return [...prev, ...response.gallery];
106
+ }
107
+ else {
108
+ // Replace all images for initial load or refresh
109
+ return response.gallery;
110
+ }
111
+ });
112
+ // Update pagination state
113
+ setCurrentPage(response.current_page);
114
+ setHasMore(response.next_page > 0 && response.gallery.length > 0);
115
+ if (!isInitialized) {
116
+ setIsInitialized(true);
117
+ }
118
+ }
119
+ catch (err) {
120
+ handleError(`load page ${pageNum}`, err);
121
+ // On error, don't change images if it's load more
122
+ if (!isLoadMore) {
123
+ setImages([]);
124
+ }
125
+ }
126
+ finally {
127
+ if (isLoadMore) {
128
+ setIsLoadingMore(false);
129
+ }
130
+ else {
131
+ setIsLoading(false);
132
+ }
133
+ }
134
+ }, [debugLog, handleError, isInitialized]);
135
+ // Load more images (next page) - spam-protected
136
+ const loadMore = useCallback(async () => {
137
+ // Prevent multiple concurrent loadMore calls
138
+ if (isLoadMoreRunningRef.current || isLoadingMore || !hasMore) {
139
+ debugLog('Load more skipped', {
140
+ isLoadMoreRunning: isLoadMoreRunningRef.current,
141
+ isLoadingMore,
142
+ hasMore
143
+ });
144
+ return;
145
+ }
146
+ // Mark as running to prevent spam
147
+ isLoadMoreRunningRef.current = true;
148
+ try {
149
+ debugLog('Loading more images...', { nextPage: currentPage + 1 });
150
+ await loadPage(currentPage + 1, true);
151
+ }
152
+ finally {
153
+ // Always reset the running flag
154
+ isLoadMoreRunningRef.current = false;
155
+ }
156
+ }, [isLoadingMore, hasMore, currentPage, loadPage, debugLog]);
157
+ // Refresh from first page
158
+ const refresh = useCallback(async () => {
159
+ debugLog('Refreshing from first page...');
160
+ setCurrentPage(1);
161
+ setHasMore(true);
162
+ await loadPage(1, false);
163
+ }, [loadPage, debugLog]);
164
+ // Reset pagination state
165
+ const reset = useCallback(() => {
166
+ debugLog('Resetting pagination state');
167
+ setImages([]);
168
+ setCurrentPage(1);
169
+ setHasMore(true);
170
+ setError(null);
171
+ setIsInitialized(false);
172
+ // Reset loadMore running flag when page changes
173
+ isLoadMoreRunningRef.current = false;
174
+ }, [debugLog]);
175
+ // Auto-load first page on initialization
176
+ useEffect(() => {
177
+ if (memoizedOptions.autoLoad && controller && firebaseUid && eventId && !isInitialized) {
178
+ debugLog('Auto-loading first page...');
179
+ loadPage(1, false);
180
+ }
181
+ }, [memoizedOptions.autoLoad, controller, firebaseUid, eventId, isInitialized, loadPage, debugLog]);
182
+ // Reset when dependencies change (if autoReset is enabled)
183
+ useEffect(() => {
184
+ if (memoizedOptions.autoReset && isInitialized) {
185
+ debugLog('Dependencies changed, resetting state');
186
+ reset();
187
+ }
188
+ }, [controller, firebaseUid, eventId, memoizedOptions.autoReset, isInitialized, reset, debugLog]);
189
+ // Paging info object - memoized to prevent re-renders
190
+ const info = useMemo(() => ({
191
+ isLoading,
192
+ isLoadingMore,
193
+ error,
194
+ currentPage,
195
+ hasMore,
196
+ totalImages: images.length,
197
+ isInitialized
198
+ }), [isLoading, isLoadingMore, error, currentPage, hasMore, images.length, isInitialized]);
199
+ // Actions object - memoized to prevent re-renders
200
+ const actions = useMemo(() => ({
201
+ loadMore,
202
+ refresh,
203
+ reset
204
+ }), [loadMore, refresh, reset]);
205
+ // Return object - memoized to prevent re-renders
206
+ return useMemo(() => ({
207
+ images,
208
+ info,
209
+ actions
210
+ }), [images, info, actions]);
211
+ }
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export { useEditorHeadless } from './lib/hooks/useEditorHeadless';
29
29
  export { useAdjustmentHistory, type UseAdjustmentHistoryReturn, type HistoryInfo, type HistoryActions, type HistoryConfig } from './hooks/useAdjustmentHistory';
30
30
  export { useAdjustmentHistoryBatch, type UseAdjustmentHistoryBatchReturn, type BatchAdjustmentState, type ImageAdjustmentConfig, type BatchHistoryInfo, type BatchHistoryActions, type BatchHistoryConfig } from './hooks/useAdjustmentHistoryBatch';
31
31
  export { usePreset, type UsePresetReturn, type PresetInfo, type PresetActions, type PresetOptions } from './hooks/usePreset';
32
+ export { usePaging, type UsePagingReturn, type PagingInfo, type PagingActions, type PagingOptions } from './hooks/usePaging';
32
33
  export { default as useColors } from './themes/colors';
33
34
  export { default as useHonchoTypography } from './themes/honchoTheme';
34
35
  export { default as useIsMobile } from './utils/isMobile';
package/dist/index.js CHANGED
@@ -28,6 +28,8 @@ export { useAdjustmentHistory } from './hooks/useAdjustmentHistory';
28
28
  export { useAdjustmentHistoryBatch } from './hooks/useAdjustmentHistoryBatch';
29
29
  // --- Preset Hook ---
30
30
  export { usePreset } from './hooks/usePreset';
31
+ // --- Paging Hook ---
32
+ export { usePaging } from './hooks/usePaging';
31
33
  // --- Theme & Utils ---
32
34
  export { default as useColors } from './themes/colors';
33
35
  export { default as useHonchoTypography } from './themes/honchoTheme';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yogiswara/honcho-editor-ui",
3
- "version": "2.5.8",
3
+ "version": "2.5.10",
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",