flex-image-viewer 1.0.0

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.
Files changed (67) hide show
  1. package/README.md +160 -0
  2. package/dist/FlexImageViewer/Content/index.js +193 -0
  3. package/dist/FlexImageViewer/Footer/Actions.js +115 -0
  4. package/dist/FlexImageViewer/Footer/index.js +56 -0
  5. package/dist/FlexImageViewer/Header/Actions.js +80 -0
  6. package/dist/FlexImageViewer/Header/index.js +42 -0
  7. package/dist/FlexImageViewer/Info/index.js +52 -0
  8. package/dist/FlexImageViewer/Thumbnail/index.js +90 -0
  9. package/dist/FlexImageViewer/index.css +582 -0
  10. package/dist/FlexImageViewer/index.js +117 -0
  11. package/dist/components/ActionItem.js +10 -0
  12. package/dist/components/BaseActions.js +69 -0
  13. package/dist/components/Icon/index.js +48 -0
  14. package/dist/components/Icon/svg/arrow-left.js +15 -0
  15. package/dist/components/Icon/svg/arrow-right.js +15 -0
  16. package/dist/components/Icon/svg/caret-right.js +15 -0
  17. package/dist/components/Icon/svg/check.js +15 -0
  18. package/dist/components/Icon/svg/clear.js +15 -0
  19. package/dist/components/Icon/svg/close.js +16 -0
  20. package/dist/components/Icon/svg/default-image.js +17 -0
  21. package/dist/components/Icon/svg/down.js +15 -0
  22. package/dist/components/Icon/svg/info-circle.js +20 -0
  23. package/dist/components/Icon/svg/rotate-left.js +20 -0
  24. package/dist/components/Icon/svg/rotate-right.js +20 -0
  25. package/dist/components/Icon/svg/save.js +15 -0
  26. package/dist/components/Icon/svg/size.js +19 -0
  27. package/dist/components/Icon/svg/thumbnail.js +19 -0
  28. package/dist/components/Icon/svg/zoom-adapt.js +24 -0
  29. package/dist/components/Icon/svg/zoom-in.js +15 -0
  30. package/dist/components/Icon/svg/zoom-out.js +15 -0
  31. package/dist/components/Image.js +148 -0
  32. package/dist/components/Modal.js +98 -0
  33. package/dist/components/PercentSelect.js +160 -0
  34. package/dist/components/ThumbnailIamge.js +65 -0
  35. package/dist/components/index.js +6 -0
  36. package/dist/context/ViewerContext.js +208 -0
  37. package/dist/hooks/useImageOperations.js +113 -0
  38. package/dist/hooks/useImageSource.js +52 -0
  39. package/dist/index.js +2 -0
  40. package/dist/rspress.config.d.ts +2 -0
  41. package/dist/src/FlexImageViewer/Content/index.d.ts +11 -0
  42. package/dist/src/FlexImageViewer/Footer/Actions.d.ts +13 -0
  43. package/dist/src/FlexImageViewer/Footer/index.d.ts +8 -0
  44. package/dist/src/FlexImageViewer/Header/Actions.d.ts +15 -0
  45. package/dist/src/FlexImageViewer/Header/index.d.ts +11 -0
  46. package/dist/src/FlexImageViewer/Info/index.d.ts +5 -0
  47. package/dist/src/FlexImageViewer/Thumbnail/index.d.ts +5 -0
  48. package/dist/src/FlexImageViewer/index.d.ts +21 -0
  49. package/dist/src/components/ActionItem.d.ts +6 -0
  50. package/dist/src/components/BaseActions.d.ts +17 -0
  51. package/dist/src/components/Icon/index.d.ts +6 -0
  52. package/dist/src/components/Image.d.ts +18 -0
  53. package/dist/src/components/Modal.d.ts +9 -0
  54. package/dist/src/components/PercentSelect.d.ts +17 -0
  55. package/dist/src/components/ThumbnailIamge.d.ts +8 -0
  56. package/dist/src/components/index.d.ts +13 -0
  57. package/dist/src/context/ViewerContext.d.ts +13 -0
  58. package/dist/src/hooks/useImageOperations.d.ts +12 -0
  59. package/dist/src/hooks/useImageSource.d.ts +7 -0
  60. package/dist/src/index.d.ts +6 -0
  61. package/dist/src/utils/dict.d.ts +1 -0
  62. package/dist/src/utils/exif.d.ts +2 -0
  63. package/dist/src/utils/source.d.ts +1 -0
  64. package/dist/utils/dict.js +16 -0
  65. package/dist/utils/exif.js +17 -0
  66. package/dist/utils/source.js +2 -0
  67. package/package.json +74 -0
@@ -0,0 +1,113 @@
1
+ import { useCallback } from "react";
2
+ const ZOOM_STEP = 0.1;
3
+ function useImageOperations(state, dispatch) {
4
+ const arrayIndex = state.currentIndex - 1;
5
+ const rotateLeft = useCallback(()=>{
6
+ dispatch({
7
+ type: 'ROTATE_LEFT',
8
+ payload: {
9
+ index: arrayIndex
10
+ }
11
+ });
12
+ }, [
13
+ dispatch,
14
+ arrayIndex
15
+ ]);
16
+ const rotateRight = useCallback(()=>{
17
+ dispatch({
18
+ type: 'ROTATE_RIGHT',
19
+ payload: {
20
+ index: arrayIndex
21
+ }
22
+ });
23
+ }, [
24
+ dispatch,
25
+ arrayIndex
26
+ ]);
27
+ const setZoom = useCallback((scale)=>{
28
+ dispatch({
29
+ type: 'SET_ZOOM',
30
+ payload: {
31
+ index: arrayIndex,
32
+ scale
33
+ }
34
+ });
35
+ }, [
36
+ dispatch,
37
+ arrayIndex
38
+ ]);
39
+ const zoomIn = useCallback((step = ZOOM_STEP)=>{
40
+ dispatch({
41
+ type: 'ZOOM_IN',
42
+ payload: {
43
+ index: arrayIndex,
44
+ step
45
+ }
46
+ });
47
+ }, [
48
+ dispatch,
49
+ arrayIndex
50
+ ]);
51
+ const zoomOut = useCallback((step = ZOOM_STEP)=>{
52
+ dispatch({
53
+ type: 'ZOOM_OUT',
54
+ payload: {
55
+ index: arrayIndex,
56
+ step
57
+ }
58
+ });
59
+ }, [
60
+ dispatch,
61
+ arrayIndex
62
+ ]);
63
+ const adaptZoom = useCallback((scale)=>{
64
+ dispatch({
65
+ type: 'ADAPT_ZOOM',
66
+ payload: {
67
+ index: arrayIndex,
68
+ scale
69
+ }
70
+ });
71
+ }, [
72
+ dispatch,
73
+ arrayIndex
74
+ ]);
75
+ const clear = useCallback(()=>{
76
+ const file = state.files[arrayIndex];
77
+ dispatch({
78
+ type: 'CLEAR_IMAGE',
79
+ payload: {
80
+ index: arrayIndex,
81
+ angle: file?.angle ?? 0,
82
+ scale: file?.scale ?? 1
83
+ }
84
+ });
85
+ }, [
86
+ dispatch,
87
+ arrayIndex,
88
+ state.files
89
+ ]);
90
+ const updateCurrentFile = useCallback((file)=>{
91
+ dispatch({
92
+ type: 'UPDATE_FILE',
93
+ payload: {
94
+ index: arrayIndex,
95
+ file: file
96
+ }
97
+ });
98
+ }, [
99
+ dispatch,
100
+ arrayIndex
101
+ ]);
102
+ return {
103
+ rotateLeft,
104
+ rotateRight,
105
+ setZoom,
106
+ zoomIn,
107
+ zoomOut,
108
+ adaptZoom,
109
+ clear,
110
+ updateCurrentFile
111
+ };
112
+ }
113
+ export { useImageOperations };
@@ -0,0 +1,52 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { DEFAULT_IMAGE } from "../utils/source.js";
3
+ import { getImageOrientation } from "../utils/exif.js";
4
+ function useImageSource(file, getSrc) {
5
+ const [src, setSrc] = useState(DEFAULT_IMAGE);
6
+ const [loading, setLoading] = useState(false);
7
+ const [orientation, setOrientation] = useState(void 0);
8
+ const requestIdRef = useRef(0);
9
+ const getSrcRef = useRef(getSrc);
10
+ getSrcRef.current = getSrc;
11
+ useEffect(()=>{
12
+ if (!file) {
13
+ setSrc(DEFAULT_IMAGE);
14
+ setLoading(false);
15
+ setOrientation(void 0);
16
+ return;
17
+ }
18
+ const currentRequestId = ++requestIdRef.current;
19
+ setOrientation(void 0);
20
+ const resolveSrc = async ()=>{
21
+ if (getSrcRef.current) return getSrcRef.current(file);
22
+ return file.src ?? DEFAULT_IMAGE;
23
+ };
24
+ let cancelled = false;
25
+ setLoading(true);
26
+ resolveSrc().then((resolvedSrc)=>{
27
+ if (!cancelled && currentRequestId === requestIdRef.current) {
28
+ setSrc(resolvedSrc);
29
+ setLoading(false);
30
+ }
31
+ if (resolvedSrc && resolvedSrc !== DEFAULT_IMAGE) getImageOrientation(resolvedSrc).then((ori)=>{
32
+ if (!cancelled && currentRequestId === requestIdRef.current) setOrientation(ori);
33
+ });
34
+ }).catch(()=>{
35
+ if (!cancelled && currentRequestId === requestIdRef.current) {
36
+ setSrc(DEFAULT_IMAGE);
37
+ setLoading(false);
38
+ }
39
+ });
40
+ return ()=>{
41
+ cancelled = true;
42
+ };
43
+ }, [
44
+ file
45
+ ]);
46
+ return {
47
+ src,
48
+ loading,
49
+ orientation
50
+ };
51
+ }
52
+ export { useImageSource };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { ActionItem } from "./components/index.js";
2
+ export { default as FlexImageViewer } from "./FlexImageViewer/index.js";
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@rspress/core").UserConfig;
2
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import type { ImageProps } from '../../components';
2
+ import type { FileData } from '../../types';
3
+ export interface ContentProps<T extends FileData> extends Pick<ImageProps<T>, 'getSrc'> {
4
+ preload?: boolean;
5
+ wheelZoom?: boolean;
6
+ wheelZoomStep?: number;
7
+ disableRightClick?: boolean;
8
+ onClose?: () => void;
9
+ }
10
+ declare function Content<T extends FileData>(props: ContentProps<T>): import("react/jsx-runtime").JSX.Element;
11
+ export default Content;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { FileData } from '../../types';
3
+ type ActionKey = 'thumbnail' | 'info' | 'zoomAdapt' | 'zoomSelect' | 'zoomOut' | 'zoomSlider' | 'zoomIn';
4
+ export interface FooterActionsProps<T extends FileData> {
5
+ zoom?: number;
6
+ updateCurrentFile?: (file: Partial<T>) => void;
7
+ renderAction?: (actions: Record<ActionKey, React.ReactNode>, context: {
8
+ zoom: number;
9
+ updateCurrentFile: (file: Partial<T>) => void;
10
+ }) => React.ReactNode;
11
+ }
12
+ declare function Actions<T extends FileData>(props: FooterActionsProps<T>): import("react/jsx-runtime").JSX.Element;
13
+ export default Actions;
@@ -0,0 +1,8 @@
1
+ import type { FileData } from '../../types';
2
+ import type { FooterActionsProps } from './Actions';
3
+ export interface FooterProps<T extends FileData> {
4
+ renderAction?: FooterActionsProps<T>['renderAction'];
5
+ updateCurrentFile?: FooterActionsProps<T>['updateCurrentFile'];
6
+ }
7
+ declare function Footer<T extends FileData>({ renderAction, updateCurrentFile }: FooterProps<T>): import("react/jsx-runtime").JSX.Element;
8
+ export default Footer;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import type { FileData } from '../../types';
3
+ type ActionKey = 'rotateLeft' | 'rotateRight' | 'clear' | 'close';
4
+ export interface HeaderActionsProps<T extends FileData> {
5
+ rotate?: number;
6
+ updateCurrentFile?: (file: Partial<T>) => void;
7
+ renderAction?: (actions: Record<ActionKey, React.ReactNode>, context: {
8
+ rotate: number;
9
+ updateCurrentFile: (file: Partial<T>) => void;
10
+ }) => React.ReactNode;
11
+ onClear?: () => void;
12
+ onClose?: (e: React.MouseEvent) => void;
13
+ }
14
+ declare function Actions<T extends FileData>(props: HeaderActionsProps<T>): import("react/jsx-runtime").JSX.Element;
15
+ export default Actions;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { FileData } from '../../types';
3
+ import type { HeaderActionsProps } from './Actions';
4
+ export interface HeaderProps<T extends FileData> {
5
+ renderAction?: HeaderActionsProps<T>['renderAction'];
6
+ onClose?: (e: React.MouseEvent) => void;
7
+ onClear?: () => void;
8
+ updateCurrentFile?: HeaderActionsProps<T>['updateCurrentFile'];
9
+ }
10
+ declare function Header<T extends FileData>({ renderAction, onClose, onClear, updateCurrentFile, }: HeaderProps<T>): import("react/jsx-runtime").JSX.Element;
11
+ export default Header;
@@ -0,0 +1,5 @@
1
+ import type { FileData } from '../../types';
2
+ export interface InfoProps {
3
+ renderInfo?: (file: FileData) => React.ReactNode;
4
+ }
5
+ export default function Info(props: InfoProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { ThumbnailImageProps } from '../../components/ThumbnailIamge';
2
+ import type { FileData } from '../../types';
3
+ type ThumbnailProps<T extends FileData> = Pick<ThumbnailImageProps<T>, 'getThumbnail'>;
4
+ export default function Thumbnail<T extends FileData>(props: ThumbnailProps<T>): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { ImageProps, ModalProps } from '../components';
2
+ import type { FileData } from '../types';
3
+ import type { ThumbnailImageProps } from '../components/ThumbnailIamge';
4
+ import type { HeaderActionsProps } from './Header/Actions';
5
+ import type { FooterActionsProps } from './Footer/Actions';
6
+ import type { InfoProps } from './Info';
7
+ import type { ContentProps } from './Content';
8
+ import 'rc-slider/assets/index.css';
9
+ import 'rc-tooltip/assets/bootstrap.css';
10
+ import './index.css';
11
+ export interface FlexImageViewerProps<T extends FileData> extends Omit<ModalProps, 'children'>, InfoProps, Pick<ThumbnailImageProps<T>, 'getThumbnail'>, Pick<ImageProps<T>, 'getSrc'>, Pick<ContentProps<T>, 'wheelZoom' | 'wheelZoomStep' | 'preload' | 'disableRightClick'> {
12
+ files?: T[];
13
+ current?: number;
14
+ loop?: boolean;
15
+ onClear?: () => void;
16
+ onIndexChange?: (index: number) => void;
17
+ headerProps?: Pick<HeaderActionsProps<T>, 'renderAction'>;
18
+ footerProps?: Pick<FooterActionsProps<T>, 'renderAction'>;
19
+ }
20
+ declare function FlexImageViewer<T extends FileData>(props: FlexImageViewerProps<T>): import("react/jsx-runtime").JSX.Element;
21
+ export default FlexImageViewer;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface ActionItemProps {
3
+ onClick?: () => void;
4
+ children: React.ReactNode;
5
+ }
6
+ export default function ActionItem({ children, onClick }: ActionItemProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export type ActionType = 'icon' | 'slider' | 'select';
3
+ export interface BaseActionItem {
4
+ key: string;
5
+ type: ActionType;
6
+ tooltipKey?: string;
7
+ iconName?: string;
8
+ tooltipPlacement?: 'topRight' | 'bottomRight' | 'topLeft' | 'bottomLeft';
9
+ valueKey?: string;
10
+ }
11
+ export interface BaseActionsProps<C extends Record<string, unknown> = Record<string, unknown>> {
12
+ items: BaseActionItem[];
13
+ contextValues: C;
14
+ renderAction?: (actions: Record<string, React.ReactNode>, context: C) => React.ReactNode;
15
+ onAction: (key: string, value?: unknown, event?: React.MouseEvent) => void;
16
+ }
17
+ export default function BaseActions<C extends Record<string, unknown> = Record<string, unknown>>(props: BaseActionsProps<C>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface IconProps extends React.SVGProps<SVGSVGElement> {
3
+ name: string;
4
+ }
5
+ declare const Icon: React.FC<IconProps>;
6
+ export default Icon;
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { FileData } from '../types';
3
+ export interface ImageProps<T extends FileData> {
4
+ file?: T;
5
+ angle?: number;
6
+ scale?: number;
7
+ getSrc?: (file: T) => Promise<string>;
8
+ isAdapt?: boolean;
9
+ onAdapt?: (zoom: number) => void;
10
+ wheelZoom?: boolean;
11
+ wheelZoomStep?: number;
12
+ onWheelZoom?: (scale: number) => void;
13
+ onOrientation?: (orientation: number) => void;
14
+ disableRightClick?: boolean;
15
+ style?: React.CSSProperties;
16
+ }
17
+ declare function Image<T extends FileData>(props: ImageProps<T>): import("react/jsx-runtime").JSX.Element;
18
+ export default Image;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface ModalProps {
3
+ visible: boolean;
4
+ onClose?: () => void;
5
+ children: React.ReactNode;
6
+ getContainer?: string | (() => HTMLElement | null) | HTMLElement | null;
7
+ }
8
+ export declare const getContainer: string | (() => HTMLElement | null) | HTMLElement | null;
9
+ export default function Modal(props: ModalProps): React.ReactPortal | null;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export declare const getScaleValue: (sliderValue: number) => number;
3
+ export declare const getSliderValue: (scale: number) => number;
4
+ export interface PercentOption {
5
+ value: number;
6
+ label: string;
7
+ }
8
+ export interface PercentSelectProps {
9
+ value?: number;
10
+ onChange?: (value: number) => void;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ style?: React.CSSProperties;
14
+ options?: number[];
15
+ }
16
+ declare function PercentSelect({ value, onChange, disabled, className, style, options, }: PercentSelectProps): import("react/jsx-runtime").JSX.Element;
17
+ export default PercentSelect;
@@ -0,0 +1,8 @@
1
+ import type { FileData } from '../types';
2
+ export interface ThumbnailImageProps<T extends FileData> {
3
+ file?: T;
4
+ angle?: number;
5
+ getThumbnail?: (file: T) => Promise<string>;
6
+ }
7
+ declare function ThumbnailImage<T extends FileData>(props: ThumbnailImageProps<T>): import("react/jsx-runtime").JSX.Element;
8
+ export default ThumbnailImage;
@@ -0,0 +1,13 @@
1
+ import Icon from './Icon';
2
+ import Image from './Image';
3
+ import Modal from './Modal';
4
+ import PercentSelect from './PercentSelect';
5
+ import ActionItem from './ActionItem';
6
+ import BaseActions from './BaseActions';
7
+ import type { IconProps } from './Icon';
8
+ import type { ImageProps } from './Image';
9
+ import type { ModalProps } from './Modal';
10
+ import type { PercentSelectProps } from './PercentSelect';
11
+ import type { ActionItemProps } from './ActionItem';
12
+ import type { BaseActionsProps, BaseActionItem } from './BaseActions';
13
+ export { Icon, IconProps, Image, ImageProps, Modal, ModalProps, PercentSelect, PercentSelectProps, ActionItem, ActionItemProps, BaseActions, BaseActionsProps, BaseActionItem, };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { ViewerState, ViewerAction, FileData } from '../types';
3
+ export declare const ViewerStateContext: React.Context<ViewerState<FileData> | null>;
4
+ export declare const ViewerDispatchContext: React.Context<React.Dispatch<ViewerAction<FileData>> | null>;
5
+ export declare function useViewerState<T extends FileData>(): ViewerState<T>;
6
+ export declare function useViewerDispatch(): React.Dispatch<ViewerAction<FileData>>;
7
+ export declare function viewerReducer<T extends FileData>(state: ViewerState<T>, action: ViewerAction<T>): ViewerState<T>;
8
+ export interface ViewerProviderProps {
9
+ children: React.ReactNode;
10
+ initialCurrent?: number;
11
+ initialFilesLength?: number;
12
+ }
13
+ export declare function ViewerProvider({ children, initialCurrent, initialFilesLength, }: ViewerProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import type { ViewerState, ViewerAction, FileData } from '../types';
2
+ export interface ImageOperations {
3
+ rotateLeft: () => void;
4
+ rotateRight: () => void;
5
+ setZoom: (scale: number) => void;
6
+ zoomIn: (step?: number) => void;
7
+ zoomOut: (step?: number) => void;
8
+ adaptZoom: (scale: number) => void;
9
+ clear: () => void;
10
+ updateCurrentFile: (file: Partial<FileData>) => void;
11
+ }
12
+ export declare function useImageOperations<T extends FileData>(state: ViewerState<T>, dispatch: React.Dispatch<ViewerAction<T>>): ImageOperations;
@@ -0,0 +1,7 @@
1
+ import type { FileData } from '../types';
2
+ export interface UseImageSourceResult {
3
+ src: string;
4
+ loading: boolean;
5
+ orientation?: number;
6
+ }
7
+ export declare function useImageSource<T extends FileData = FileData>(file?: T, getSrc?: (file: T) => Promise<string>): UseImageSourceResult;
@@ -0,0 +1,6 @@
1
+ import { ActionItem } from './components';
2
+ import FlexImageViewer from './FlexImageViewer';
3
+ import type { FileData } from './types';
4
+ import type { FlexImageViewerProps } from './FlexImageViewer';
5
+ import type { ActionItemProps } from './components';
6
+ export { FlexImageViewer, FlexImageViewerProps, ActionItem, ActionItemProps, FileData };
@@ -0,0 +1 @@
1
+ export declare const TOOLTIP_CONFIG: Record<string, string>;
@@ -0,0 +1,2 @@
1
+ export declare function getImageOrientation(source: string | Blob | ArrayBuffer): Promise<number | undefined>;
2
+ export declare function shouldSwapDimensions(orientation?: number): boolean;
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_IMAGE = "data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMC8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgaWQ9ImJvZHlfMSIgd2lkdGg9IjE5NCIgaGVpZ2h0PSIxOTUiPgoKPGcgdHJhbnNmb3JtPSJtYXRyaXgoMS4zMzMzMzM0IDAgMCAxLjMzMzMzMzQgMCAwKSI+Cgk8aW1hZ2UgIHg9IjAiIHk9IjAiIHhsaW5rOmhyZWY9ImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBTUlBQUFERENBWUFBQURRdmM2VUFBQUFDWEJJV1hNQUFBN0RBQUFPd3dISGI2aGtBQUFHT0VsRVFWUjRuT3pkcTA0a2F3QkZZVjRkaDhQaFFPSEE0WEE0SEE2SncrRjRBQ1kvQ1FrMDA5MUZYMnJ2aGs4c2RVNFlzbmF0cmt2WFpJNWVYMS9md01GZlB3YU8wcjhBT0dnNEJvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1N3UXM3VDA5UGI3ZTN0citEeDhURitzRFVqaENWaVhsNWUzbzZQajM4Tkp5Y244WU90R1NFc0VUTStRZE1INzY1eFZoRENqejhoaEpEL2xIWkdLQkFqaFB3R1FpZ1FJNFQ4QmtJb0VDT0UvQVpDS0JBamhQd0dRdmdGSVp5ZG5iMC9za3cvS2ZMVWFOcmVIcC91T0lTN3U3c3ZQK2Y1K2ZudDh2SXlIb0hIcDBLWTdZd3d2c0ZkOXVYYzZlbXBFQXJPOU00SU00U3c2Z3VybTVzYklSUWM4RUtZSVlSVlAyK2NMVndhNVE5NElUZ2plTVZDQ05sN2hJRjdoUHludmhCbUNtRThMbjE0ZVBoMm8zeDlmUjIvTFBMVVNBaXpmNDh3UHYwdkxpN2VTUi84UW5CR09PaHZsc2ZaWmRlWFUxN0Rkck44Y0NHTVAzOWNWbzF2cUlYZ0h1RlBobkIvZi8vbHI0dnU2bFVOWndSbmhJTUo0ZXJxNnR2dk1tSVFncWRHQjN0RytPbGx6Zm41K2RMZlo1d2xuQkgydDdlWDd2WVV3c2QzQ2xOakdQL2Z1Q2RZTmRhMnIybTROQkxDckdlRXo1YzNVMTY0Ry9jQTR5M1ZLYi9YTm0reUNrRUlzNFh3djAvMmRUZTg0NzlQL2IyMmVaSWtCQ0hNRXNJNDJKZGQzaXo3ZVorZkVQMGtoazJlSkFsQkNIc1BZUnlZNno3WkYyOTRWNzJidEk1Tkhxc0tRUWg3RDJIeEhhTmxqUHVIeGZ1SVRmbnBreVFoQ0dHdklTeis5Y3gxaktjLzY1NFFUV1g4MlVMWTNxUEhwMXVHc0l0UDltMzVPTXM0SXdnaEVzS3FMOERtWnNxVEpKZEd6Z2c3RDJIS0YyQnpNdVd4cWhDRXNOTVFwandoU3JEdVNaSVFoTERURUpvUHFFUDl2ZE80V2Q3Z2dFci82emZyRUlJUVp2bGtQVlNjRVp3UmhDQUVsMGJPQ000STdoRmNHcmswY3JQc0hzRTlncWRHVzkwcysrZGw4NDgwUFQ0dEVQUHhCZFY0UVc2OFNuSElqSGVSUERGeXN4d1BDdjBPZktGV01BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEdjRCQUFELy96VjJpUGdBQUFBR1NVUkJWQU1BbEwzNEprZXVucFlBQUFBQVNVVk9SSzVDWUlJPSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgd2lkdGg9IjE0NS41IiBoZWlnaHQ9IjE0Ni4yNSIvPgo8L2c+Cjwvc3ZnPgo=";
@@ -0,0 +1,16 @@
1
+ const TOOLTIP_CONFIG = {
2
+ arrowLeft: '查看上一张图片',
3
+ arrowRight: '查看下一张图片',
4
+ clear: '重置图片',
5
+ close: '关闭',
6
+ thumbnail: '显示或隐藏缩略图',
7
+ info: '显示或隐藏图片信息',
8
+ rotateLeft: '向左旋转图片',
9
+ rotateRight: '向右旋转图片',
10
+ zoomAdapt: '自适应缩放',
11
+ zoomSelect: '选择缩放比例',
12
+ zoomOut: '缩小图片',
13
+ zoomSlider: '调整缩放比例',
14
+ zoomIn: '放大图片'
15
+ };
16
+ export { TOOLTIP_CONFIG };
@@ -0,0 +1,17 @@
1
+ import exifreader from "exifreader";
2
+ async function getImageOrientation(source) {
3
+ try {
4
+ let input;
5
+ input = 'string' == typeof source ? source : source instanceof Blob ? await source.arrayBuffer() : source;
6
+ const tags = 'string' == typeof input ? await exifreader.load(input) : await exifreader.load(input);
7
+ const orientation = tags?.Orientation?.value;
8
+ if ('number' == typeof orientation) return orientation;
9
+ return;
10
+ } catch {
11
+ return;
12
+ }
13
+ }
14
+ function shouldSwapDimensions(orientation) {
15
+ return 6 === orientation || 8 === orientation;
16
+ }
17
+ export { getImageOrientation, shouldSwapDimensions };
@@ -0,0 +1,2 @@
1
+ const DEFAULT_IMAGE = 'data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMC8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgaWQ9ImJvZHlfMSIgd2lkdGg9IjE5NCIgaGVpZ2h0PSIxOTUiPgoKPGcgdHJhbnNmb3JtPSJtYXRyaXgoMS4zMzMzMzM0IDAgMCAxLjMzMzMzMzQgMCAwKSI+Cgk8aW1hZ2UgIHg9IjAiIHk9IjAiIHhsaW5rOmhyZWY9ImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBTUlBQUFERENBWUFBQURRdmM2VUFBQUFDWEJJV1hNQUFBN0RBQUFPd3dISGI2aGtBQUFHT0VsRVFWUjRuT3pkcTA0a2F3QkZZVjRkaDhQaFFPSEE0WEE0SEE2SncrRjRBQ1kvQ1FrMDA5MUZYMnJ2aGs4c2RVNFlzbmF0cmt2WFpJNWVYMS9md01GZlB3YU8wcjhBT0dnNEJvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1N3UXM3VDA5UGI3ZTN0citEeDhURitzRFVqaENWaVhsNWUzbzZQajM4Tkp5Y244WU90R1NFc0VUTStRZE1INzY1eFZoRENqejhoaEpEL2xIWkdLQkFqaFB3R1FpZ1FJNFQ4QmtJb0VDT0UvQVpDS0JBamhQd0dRdmdGSVp5ZG5iMC9za3cvS2ZMVWFOcmVIcC91T0lTN3U3c3ZQK2Y1K2ZudDh2SXlIb0hIcDBLWTdZd3d2c0ZkOXVYYzZlbXBFQXJPOU00SU00U3c2Z3VybTVzYklSUWM4RUtZSVlSVlAyK2NMVndhNVE5NElUZ2plTVZDQ05sN2hJRjdoUHludmhCbUNtRThMbjE0ZVBoMm8zeDlmUjIvTFBMVVNBaXpmNDh3UHYwdkxpN2VTUi84UW5CR09PaHZsc2ZaWmRlWFUxN0Rkck44Y0NHTVAzOWNWbzF2cUlYZ0h1RlBobkIvZi8vbHI0dnU2bFVOWndSbmhJTUo0ZXJxNnR2dk1tSVFncWRHQjN0RytPbGx6Zm41K2RMZlo1d2xuQkgydDdlWDd2WVV3c2QzQ2xOakdQL2Z1Q2RZTmRhMnIybTROQkxDckdlRXo1YzNVMTY0Ry9jQTR5M1ZLYi9YTm0reUNrRUlzNFh3djAvMmRUZTg0NzlQL2IyMmVaSWtCQ0hNRXNJNDJKZGQzaXo3ZVorZkVQMGtoazJlSkFsQkNIc1BZUnlZNno3WkYyOTRWNzJidEk1Tkhxc0tRUWg3RDJIeEhhTmxqUHVIeGZ1SVRmbnBreVFoQ0dHdklTeis5Y3gxaktjLzY1NFFUV1g4MlVMWTNxUEhwMXVHc0l0UDltMzVPTXM0SXdnaEVzS3FMOERtWnNxVEpKZEd6Z2c3RDJIS0YyQnpNdVd4cWhDRXNOTVFwandoU3JEdVNaSVFoTERURUpvUHFFUDl2ZE80V2Q3Z2dFci82emZyRUlJUVp2bGtQVlNjRVp3UmhDQUVsMGJPQ000STdoRmNHcmswY3JQc0hzRTlncWRHVzkwcysrZGw4NDgwUFQ0dEVQUHhCZFY0UVc2OFNuSElqSGVSUERGeXN4d1BDdjBPZktGV01BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEb1JRTUFMeURvUlFNQUx5RG9SUU1BTHlEdjRCQUFELy96VjJpUGdBQUFBR1NVUkJWQU1BbEwzNEprZXVucFlBQUFBQVNVVk9SSzVDWUlJPSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgd2lkdGg9IjE0NS41IiBoZWlnaHQ9IjE0Ni4yNSIvPgo8L2c+Cjwvc3ZnPgo=';
2
+ export { DEFAULT_IMAGE };
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "flex-image-viewer",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "author": {
6
+ "name": "linie1119",
7
+ "email": "linie_1119@qq.com"
8
+ },
9
+ "description": "A React component for image viewer",
10
+ "license": "MIT",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "rslib",
23
+ "build:storybook": "storybook build",
24
+ "dev": "rslib --watch",
25
+ "doc": "rspress dev",
26
+ "doc:build": "rspress build",
27
+ "doc:preview": "rspress preview",
28
+ "format": "prettier --write .",
29
+ "lint": "rslint",
30
+ "storybook": "storybook dev",
31
+ "test": "rstest",
32
+ "test:watch": "rstest --watch"
33
+ },
34
+ "devDependencies": {
35
+ "@rsbuild/core": "2.0.0-rc.3",
36
+ "@rsbuild/plugin-react": "^1.4.6",
37
+ "@rsbuild/plugin-svgr": "^1.3.1",
38
+ "@rslib/core": "^0.21.2",
39
+ "@rslint/core": "^0.4.2",
40
+ "@rspress/core": "^2.0.9",
41
+ "@rspress/plugin-api-docgen": "^2.0.9",
42
+ "@rspress/plugin-preview": "^2.0.9",
43
+ "@rstest/adapter-rslib": "^0.2.2",
44
+ "@rstest/core": "^0.9.7",
45
+ "@storybook/addon-docs": "^10.3.5",
46
+ "@storybook/addon-onboarding": "^10.3.5",
47
+ "@storybook/react": "^10.3.5",
48
+ "@testing-library/jest-dom": "^6.9.1",
49
+ "@testing-library/react": "^16.3.2",
50
+ "@types/node": "^24.12.2",
51
+ "@types/react": "^19.2.14",
52
+ "@types/react-dom": "^19.2.3",
53
+ "happy-dom": "^20.9.0",
54
+ "prettier": "^3.8.2",
55
+ "react": "^19.2.5",
56
+ "react-dom": "^19.2.5",
57
+ "rsbuild-plugin-workspace-dev": "^0.1.4",
58
+ "storybook": "^10.3.5",
59
+ "storybook-addon-rslib": "^3.3.3",
60
+ "storybook-react-rsbuild": "^3.3.3",
61
+ "typescript": "^6.0.2"
62
+ },
63
+ "peerDependencies": {
64
+ "react": ">=16.9.0",
65
+ "react-dom": ">=16.9.0"
66
+ },
67
+ "dependencies": {
68
+ "ahooks": "^3.9.7",
69
+ "es-toolkit": "^1.46.0",
70
+ "exifreader": "^4.41.0",
71
+ "rc-slider": "^11.1.9",
72
+ "rc-tooltip": "^6.4.0"
73
+ }
74
+ }