react-images-extended-2-stable
Version:
A simple, responsive lightbox component for displaying an array of images with React.js with extended features ((stable version))
422 lines (421 loc) • 14.8 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useContext, useReducer, useCallback, useEffect, } from "react";
import { debuginfo } from "./utils/log";
import { flipManipulation, handleReset, rotateManipulation, zoomManipulation, zoomManipulationToPoint, zoomToAFactor, } from "./utils/manipulation";
export var IImageViewMode;
(function (IImageViewMode) {
IImageViewMode["READER"] = "READER";
IImageViewMode["IMAGE"] = "IMAGE";
})(IImageViewMode || (IImageViewMode = {}));
export var ILightboxImageType;
(function (ILightboxImageType) {
ILightboxImageType["IMAGE"] = "IMAGE";
ILightboxImageType["PDF"] = "PDF";
ILightboxImageType["UNINITIALISED"] = "UNINITIALISED";
})(ILightboxImageType || (ILightboxImageType = {}));
// Default state
const defaultState = {
// Figure sources:
images: [],
pdfDocumentSrc: "",
// Figure state:
currentIndex: 0,
pageCount: 0,
pinnedFigureStates: [],
currentIndexIsPinned: false,
// View mode flags:
sourceType: ILightboxImageType.UNINITIALISED,
viewMode: IImageViewMode.IMAGE,
// Figure manipulation state
isDraggingFigure: false,
figureManipulation: {
imageLoaded: false,
error: null,
left: 0,
top: 15,
width: 0,
height: 0,
rotate: 0,
imageWidth: 0,
imageHeight: 0,
scaleX: 1,
scaleY: 1,
},
showThumbnails: false,
resetImageOnLoad: true,
holdZoomDelay: 250, // ms
holdZoomInternal: 100, // ms
isLoading: false,
};
// Reducer function for state mutations
function lightboxReducer(state, action) {
switch (action.type) {
case "ZOOM_IN":
debuginfo("Handling zoom in");
const stateOnZoomIn = zoomManipulation(false, state.figureManipulation);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnZoomIn,
},
};
case "ZOOM_OUT":
debuginfo("Handling zoom out");
const stateOnZoomOut = zoomManipulation(true, state.figureManipulation);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnZoomOut,
},
};
case "ZOOM_IN_TO_POINT":
debuginfo("Handling zoom in to particular point");
const stateOnZoomInToPoint = zoomManipulationToPoint(state.figureManipulation, action.payload);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnZoomInToPoint,
},
};
case "ZOOM_TO_FACTOR":
const zoomToFactor = zoomToAFactor(action.payload);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...zoomToFactor,
},
};
case "ROTATE_LEFT":
debuginfo("Handling rotate left");
const stateOnRotateLeft = rotateManipulation(state.figureManipulation, false);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnRotateLeft,
},
};
case "ROTATE_RIGHT":
debuginfo("Handling rotate right");
const stateOnRotateRight = rotateManipulation(state.figureManipulation, true);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnRotateRight,
},
};
case "FLIP_HORIZONTAL":
debuginfo("Handling flip horizontal");
const stateOnFlipHorisontal = flipManipulation(state.figureManipulation, true);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnFlipHorisontal,
},
};
case "FLIP_VERTICAL":
debuginfo("Handling flip vertical");
const stateOnFlipVertical = flipManipulation(state.figureManipulation, false);
return {
...state,
figureManipulation: {
...state.figureManipulation,
...stateOnFlipVertical,
},
};
case "SET_STATE":
return {
...state,
...action.payload,
};
case "SET_IMAGES":
return {
...state,
images: action.payload,
};
case "SET_CURRENT_INDEX":
return {
...state,
currentIndexIsPinned: false,
currentIndex: Math.max(0, Math.min(action.payload, state.pageCount - 1)),
};
case "SET_PAGE_COUNT":
return {
...state,
pageCount: action.payload,
};
case "UPDATE_VIEW_STATE":
return {
...state,
viewMode: action.payload,
isDraggingFigure: false,
};
case "SET_SOURCE_TYPE":
return {
...state,
sourceType: action.payload,
isDraggingFigure: false,
};
case "PIN_FIGURE_STATE":
return {
...state,
pinnedFigureStates: [...state.pinnedFigureStates, action.payload],
};
case "UNPIN_FIGURE_STATE":
const unpinned = state.pinnedFigureStates.filter((_pin, index) => index !== action.payload);
return {
...state,
pinnedFigureStates: unpinned,
};
case "UPDATE_FIGURE_STATE":
return {
...state,
figureManipulation: {
...state.figureManipulation,
...action.payload,
},
};
case "SET_SHOW_THUMBNAILS":
return {
...state,
showThumbnails: action.payload,
};
case "SET_LOADING":
return {
...state,
isLoading: action.payload,
};
case "SET_DRAGGING":
return {
...state,
isDraggingFigure: action.payload,
};
case "RESET_IMAGE":
debuginfo("Resetting image state");
const stateOnImageReset = handleReset(state);
return {
...state,
isDraggingFigure: false,
figureManipulation: {
...state.figureManipulation,
...stateOnImageReset,
},
};
case "GO_TO_PINNED_FIGURE_STATE":
debuginfo(`Going to pinned image at index: ${action.payload.index}`);
const { index, updates } = action.payload;
return {
...state,
currentIndex: index,
currentIndexIsPinned: true,
figureManipulation: {
...state.figureManipulation,
...updates,
},
};
case "RESET_ALL":
return defaultState;
default:
return state;
}
}
// Create context
const LightboxContext = createContext(undefined);
export const LightboxProvider = ({ children, initialState = {}, }) => {
const [state, dispatch] = useReducer(lightboxReducer, {
...defaultState,
...initialState,
});
const setState = useCallback((newState) => {
dispatch({ type: "SET_STATE", payload: newState });
}, []);
// Convenience methods
const setImages = useCallback((images) => {
dispatch({ type: "SET_IMAGES", payload: images });
}, []);
const setCurrentIndex = useCallback((index) => {
dispatch({ type: "SET_CURRENT_INDEX", payload: index });
}, []);
const setPageCount = useCallback((pages) => {
dispatch({ type: "SET_PAGE_COUNT", payload: pages });
}, []);
const updateFigureManipulation = useCallback((updates) => {
dispatch({ type: "UPDATE_FIGURE_STATE", payload: updates });
}, []);
const zoomIn = useCallback(() => {
debuginfo("Zoom in callback triggered");
dispatch({ type: "ZOOM_IN", payload: null });
}, []);
const zoomOut = useCallback(() => {
debuginfo("Zoom out callback triggered");
dispatch({ type: "ZOOM_OUT", payload: null });
}, []);
const zoomInToPoint = useCallback((position) => {
dispatch({ type: "ZOOM_IN_TO_POINT", payload: position });
}, []);
const zoomInToFactor = useCallback((notch) => {
dispatch({ type: "ZOOM_TO_FACTOR", payload: notch });
}, []);
const rotateLeft = useCallback(() => {
debuginfo("Rotate left callback triggered");
dispatch({ type: "ROTATE_LEFT", payload: null });
}, []);
const flipVertical = useCallback(() => {
debuginfo("Flip vertical callback triggered");
dispatch({ type: "FLIP_VERTICAL", payload: null });
}, []);
const flipHorisontal = useCallback(() => {
debuginfo("Flip horisontal callback triggered");
dispatch({ type: "FLIP_HORIZONTAL", payload: null });
}, []);
const rotateRight = useCallback(() => {
debuginfo("Rotate right callback triggered");
dispatch({ type: "ROTATE_RIGHT", payload: null });
}, []);
const setDraggingFigure = useCallback((isDragging) => {
dispatch({ type: "SET_DRAGGING", payload: isDragging });
}, []);
const resetMaipulationState = useCallback(() => {
dispatch({ type: "RESET_IMAGE" });
}, []);
const resetAll = useCallback(() => {
dispatch({ type: "RESET_ALL" });
}, []);
const updateViewState = useCallback((viewMode) => {
debuginfo(`Updating view state to: ${viewMode}`);
dispatch({ type: "UPDATE_VIEW_STATE", payload: viewMode });
dispatch({ type: "RESET_IMAGE" });
}, []);
const setSourceType = useCallback((sourceType) => {
debuginfo(`Updating view state to: ${sourceType}`);
dispatch({ type: "SET_SOURCE_TYPE", payload: sourceType });
dispatch({ type: "RESET_IMAGE" });
}, []);
const pinFigure = useCallback((state) => {
debuginfo(`Pinning image at index: ${state.imageIndex}`);
dispatch({ type: "PIN_FIGURE_STATE", payload: state });
}, []);
const unPinFigure = useCallback((imageIndex) => {
debuginfo(`Unpinning image at index: ${imageIndex}`);
dispatch({ type: "UNPIN_FIGURE_STATE", payload: imageIndex });
}, []);
const setLoading = useCallback((isLoading) => {
debuginfo(`Setting loading state to: ${isLoading}`);
dispatch({ type: "SET_LOADING", payload: isLoading });
}, []);
const goToPinnedFigure = useCallback((index, updates) => {
debuginfo(`Going to pinned image at index: ${index}`);
dispatch({
type: "GO_TO_PINNED_FIGURE_STATE",
payload: { index, updates },
});
}, []);
const contextValue = {
state,
dispatch,
setState,
setImages,
setCurrentIndex,
zoomIn,
zoomOut,
zoomInToPoint,
zoomInToFactor,
flipVertical,
flipHorisontal,
rotateLeft,
rotateRight,
updateFigureManipulation,
setDraggingFigure,
resetMaipulationState,
resetAll,
updateViewState,
goToPinnedFigure,
pinFigure,
unPinFigure,
setLoading,
setSourceType,
setPageCount,
};
return (_jsx(LightboxContext.Provider, { value: contextValue, children: children }));
};
export const useSetupState = (initialState) => {
const context = useContext(LightboxContext);
if (!context) {
throw new Error("useInitialiseState must be used within a LightboxProvider");
}
// Only initialize once when the component mounts
useEffect(() => {
context.setState(initialState);
}, []); // Empty dependency array ensures this only runs once
};
// Custom hook to use the lightbox context
export const useLightboxState = () => {
const context = useContext(LightboxContext);
if (context === undefined) {
throw new Error("useLightboxState must be used within a LightboxProvider");
}
return context;
};
// Custom hooks for specific state slices
export const useCurrentImage = () => {
const { state } = useLightboxState();
const { images, currentIndex } = state;
return images[currentIndex];
};
export const useCallbackMethods = () => {
const { state } = useLightboxState();
const { onCLickFigure, onClickNext, onClickPrev, onClose, onRotateLeft, onRotateRight, onZoomIn, onZoomOut, onSave, onClickThumbnail, } = state;
return {
onCLickFigure,
onClickNext,
onClickPrev,
onClose,
onRotateLeft,
onRotateRight,
onZoomIn,
onZoomOut,
onSave,
onClickThumbnail,
};
};
// Custom hooks for specific state slices
export const useLightboxImages = () => {
const { state, setImages, setCurrentIndex } = useLightboxState();
return {
images: state.images,
currentIndex: state.currentIndex,
pageCount: state.pageCount,
currentFigureData: state.images[state.currentIndex],
setImages,
setCurrentIndex,
nextImage: () => setCurrentIndex(state.currentIndex + 1),
prevImage: () => setCurrentIndex(state.currentIndex - 1),
toImage: (index) => setCurrentIndex(index),
hasNext: state.currentIndex < state.images.length - 1,
hasPrev: state.currentIndex > 0,
};
};
export const useLightboxManipulationState = () => {
const { state, updateFigureManipulation, resetMaipulationState } = useLightboxState();
return {
manipulationState: state.figureManipulation,
updateFigureManipulation,
resetMaipulationState,
isLoaded: state.figureManipulation.imageLoaded,
hasError: !!state.figureManipulation.error,
};
};
export const useLightboxDrag = () => {
const { state, setDraggingFigure } = useLightboxState();
return {
isDragging: state.isDraggingFigure,
setDraggingFigure,
isAnyDragging: state.isDraggingFigure,
};
};