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.
- package/README.md +160 -0
- package/dist/FlexImageViewer/Content/index.js +193 -0
- package/dist/FlexImageViewer/Footer/Actions.js +115 -0
- package/dist/FlexImageViewer/Footer/index.js +56 -0
- package/dist/FlexImageViewer/Header/Actions.js +80 -0
- package/dist/FlexImageViewer/Header/index.js +42 -0
- package/dist/FlexImageViewer/Info/index.js +52 -0
- package/dist/FlexImageViewer/Thumbnail/index.js +90 -0
- package/dist/FlexImageViewer/index.css +582 -0
- package/dist/FlexImageViewer/index.js +117 -0
- package/dist/components/ActionItem.js +10 -0
- package/dist/components/BaseActions.js +69 -0
- package/dist/components/Icon/index.js +48 -0
- package/dist/components/Icon/svg/arrow-left.js +15 -0
- package/dist/components/Icon/svg/arrow-right.js +15 -0
- package/dist/components/Icon/svg/caret-right.js +15 -0
- package/dist/components/Icon/svg/check.js +15 -0
- package/dist/components/Icon/svg/clear.js +15 -0
- package/dist/components/Icon/svg/close.js +16 -0
- package/dist/components/Icon/svg/default-image.js +17 -0
- package/dist/components/Icon/svg/down.js +15 -0
- package/dist/components/Icon/svg/info-circle.js +20 -0
- package/dist/components/Icon/svg/rotate-left.js +20 -0
- package/dist/components/Icon/svg/rotate-right.js +20 -0
- package/dist/components/Icon/svg/save.js +15 -0
- package/dist/components/Icon/svg/size.js +19 -0
- package/dist/components/Icon/svg/thumbnail.js +19 -0
- package/dist/components/Icon/svg/zoom-adapt.js +24 -0
- package/dist/components/Icon/svg/zoom-in.js +15 -0
- package/dist/components/Icon/svg/zoom-out.js +15 -0
- package/dist/components/Image.js +148 -0
- package/dist/components/Modal.js +98 -0
- package/dist/components/PercentSelect.js +160 -0
- package/dist/components/ThumbnailIamge.js +65 -0
- package/dist/components/index.js +6 -0
- package/dist/context/ViewerContext.js +208 -0
- package/dist/hooks/useImageOperations.js +113 -0
- package/dist/hooks/useImageSource.js +52 -0
- package/dist/index.js +2 -0
- package/dist/rspress.config.d.ts +2 -0
- package/dist/src/FlexImageViewer/Content/index.d.ts +11 -0
- package/dist/src/FlexImageViewer/Footer/Actions.d.ts +13 -0
- package/dist/src/FlexImageViewer/Footer/index.d.ts +8 -0
- package/dist/src/FlexImageViewer/Header/Actions.d.ts +15 -0
- package/dist/src/FlexImageViewer/Header/index.d.ts +11 -0
- package/dist/src/FlexImageViewer/Info/index.d.ts +5 -0
- package/dist/src/FlexImageViewer/Thumbnail/index.d.ts +5 -0
- package/dist/src/FlexImageViewer/index.d.ts +21 -0
- package/dist/src/components/ActionItem.d.ts +6 -0
- package/dist/src/components/BaseActions.d.ts +17 -0
- package/dist/src/components/Icon/index.d.ts +6 -0
- package/dist/src/components/Image.d.ts +18 -0
- package/dist/src/components/Modal.d.ts +9 -0
- package/dist/src/components/PercentSelect.d.ts +17 -0
- package/dist/src/components/ThumbnailIamge.d.ts +8 -0
- package/dist/src/components/index.d.ts +13 -0
- package/dist/src/context/ViewerContext.d.ts +13 -0
- package/dist/src/hooks/useImageOperations.d.ts +12 -0
- package/dist/src/hooks/useImageSource.d.ts +7 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/utils/dict.d.ts +1 -0
- package/dist/src/utils/exif.d.ts +2 -0
- package/dist/src/utils/source.d.ts +1 -0
- package/dist/utils/dict.js +16 -0
- package/dist/utils/exif.js +17 -0
- package/dist/utils/source.js +2 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# flex-image-viewer
|
|
2
|
+
|
|
3
|
+
一个功能丰富的 React 图片查看器组件,支持缩放、旋转、缩略图导航、信息面板等交互功能。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **图片浏览** — 支持多张图片切换浏览,可配置循环模式
|
|
8
|
+
- **缩放操作** — 支持放大、缩小、自适应缩放,以及鼠标滚轮缩放
|
|
9
|
+
- **旋转功能** — 支持左旋、右旋
|
|
10
|
+
- **缩略图导航** — 底部缩略图列表,快速定位图片
|
|
11
|
+
- **信息面板** — 展示图片详细信息(尺寸、大小等)
|
|
12
|
+
- **EXIF 方向识别** — 自动识别并应用图片的 EXIF 方向信息
|
|
13
|
+
- **预加载** — 支持图片预加载以提升浏览体验
|
|
14
|
+
- **禁用右键** — 支持禁用图片右键菜单
|
|
15
|
+
- **自定义操作** — 头部和底部操作栏支持自定义渲染
|
|
16
|
+
- **加载状态** — 图片加载时显示加载指示器
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 使用
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { FlexImageViewer, ActionItem } from 'flex-image-viewer';
|
|
28
|
+
import type { FileData } from 'flex-image-viewer';
|
|
29
|
+
|
|
30
|
+
function App() {
|
|
31
|
+
const [visible, setVisible] = useState(false);
|
|
32
|
+
const [current, setCurrent] = useState(1);
|
|
33
|
+
|
|
34
|
+
const files: FileData[] = [
|
|
35
|
+
{
|
|
36
|
+
name: 'image1.png',
|
|
37
|
+
src: 'https://example.com/image1.png',
|
|
38
|
+
alt: '图片1',
|
|
39
|
+
width: 1920,
|
|
40
|
+
height: 1080,
|
|
41
|
+
size: '2.5 MB',
|
|
42
|
+
infoData: [
|
|
43
|
+
{ label: '尺寸', value: '1920x1080' },
|
|
44
|
+
{ label: '格式', value: 'PNG' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<button onClick={() => setVisible(true)}>打开查看器</button>
|
|
52
|
+
<FlexImageViewer
|
|
53
|
+
files={files}
|
|
54
|
+
visible={visible}
|
|
55
|
+
current={current}
|
|
56
|
+
onClose={() => setVisible(false)}
|
|
57
|
+
onIndexChange={setCurrent}
|
|
58
|
+
wheelZoom
|
|
59
|
+
disableRightClick={false}
|
|
60
|
+
/>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Props
|
|
67
|
+
|
|
68
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
69
|
+
| ----------------- | ---------------------------------------- | ------- | ----------------------------- |
|
|
70
|
+
| files | `FileData[]` | `[]` | 图片数据列表 |
|
|
71
|
+
| current | `number` | `1` | 当前显示的图片索引(从1开始) |
|
|
72
|
+
| visible | `boolean` | - | 是否显示查看器 |
|
|
73
|
+
| loop | `boolean` | `false` | 是否循环浏览 |
|
|
74
|
+
| onClose | `() => void` | - | 关闭回调 |
|
|
75
|
+
| onIndexChange | `(index: number) => void` | - | 图片切换回调 |
|
|
76
|
+
| onClear | `() => void` | - | 重置操作回调 |
|
|
77
|
+
| wheelZoom | `boolean` | `true` | 是否启用鼠标滚轮缩放 |
|
|
78
|
+
| wheelZoomStep | `number` | `0.1` | 滚轮缩放步长 |
|
|
79
|
+
| preload | `boolean` | `false` | 是否预加载图片 |
|
|
80
|
+
| disableRightClick | `boolean` | `false` | 是否禁用右键菜单 |
|
|
81
|
+
| getSrc | `(file: T) => Promise<string> \| string` | - | 自定义获取原图地址 |
|
|
82
|
+
| getThumbnail | `(file: T) => Promise<string> \| string` | - | 自定义获取缩略图地址 |
|
|
83
|
+
| renderInfo | `(file: T) => ReactNode` | - | 自定义信息面板渲染 |
|
|
84
|
+
| headerProps | `{ renderAction? }` | - | 头部操作栏配置 |
|
|
85
|
+
| footerProps | `{ renderAction? }` | - | 底部操作栏配置 |
|
|
86
|
+
|
|
87
|
+
## FileData 数据结构
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
interface FileData {
|
|
91
|
+
name?: string; // 文件名
|
|
92
|
+
description?: string; // 描述
|
|
93
|
+
src?: string; // 图片地址
|
|
94
|
+
alt?: string; // 替代文本
|
|
95
|
+
size?: string; // 文件大小
|
|
96
|
+
width?: string | number; // 图片宽度
|
|
97
|
+
height?: string | number; // 图片高度
|
|
98
|
+
angle?: number; // 旋转角度
|
|
99
|
+
scale?: number; // 缩放比例
|
|
100
|
+
infoData?: { label: string; value: string }[]; // 信息面板数据
|
|
101
|
+
orientation?: number; // EXIF 方向值
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 开发
|
|
106
|
+
|
|
107
|
+
### 启动开发模式
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pnpm run dev
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 构建
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm run build
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 运行测试
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pnpm run test
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 启动 Storybook
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pnpm run storybook
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 启动文档站点
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pnpm run doc
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 代码检查
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm run lint
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 代码格式化
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pnpm run format
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 技术栈
|
|
150
|
+
|
|
151
|
+
- React 19
|
|
152
|
+
- TypeScript
|
|
153
|
+
- Rslib(构建工具)
|
|
154
|
+
- ahooks
|
|
155
|
+
- es-toolkit
|
|
156
|
+
- rc-slider / rc-tooltip
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { useViewerDispatch, useViewerState } from "../../context/ViewerContext.js";
|
|
4
|
+
import { Icon, Image } from "../../components/index.js";
|
|
5
|
+
function Content(props) {
|
|
6
|
+
const { preload = true, wheelZoom, wheelZoomStep, disableRightClick, getSrc } = props;
|
|
7
|
+
const state = useViewerState();
|
|
8
|
+
const dispatch = useViewerDispatch();
|
|
9
|
+
const current = state.currentIndex;
|
|
10
|
+
const files = state.files;
|
|
11
|
+
const imageOptions = state.imageOptions;
|
|
12
|
+
const [displayIndices, setDisplayIndices] = useState([]);
|
|
13
|
+
useEffect(()=>{
|
|
14
|
+
const index = current - 1;
|
|
15
|
+
if (!files || 0 === files.length) return void setDisplayIndices([]);
|
|
16
|
+
let list = [];
|
|
17
|
+
if (preload) {
|
|
18
|
+
const prevIndex = index > 0 ? index - 1 : null;
|
|
19
|
+
const nextIndex = index < files.length - 1 ? index + 1 : null;
|
|
20
|
+
list = [
|
|
21
|
+
prevIndex,
|
|
22
|
+
index,
|
|
23
|
+
nextIndex
|
|
24
|
+
];
|
|
25
|
+
} else list = [
|
|
26
|
+
index
|
|
27
|
+
];
|
|
28
|
+
const newIndices = list.filter((idx)=>null !== idx);
|
|
29
|
+
setDisplayIndices(newIndices);
|
|
30
|
+
}, [
|
|
31
|
+
current,
|
|
32
|
+
files,
|
|
33
|
+
preload
|
|
34
|
+
]);
|
|
35
|
+
const index = current - 1;
|
|
36
|
+
const handleAdapt = useCallback((zoom)=>{
|
|
37
|
+
dispatch({
|
|
38
|
+
type: 'ADAPT_ZOOM',
|
|
39
|
+
payload: {
|
|
40
|
+
index,
|
|
41
|
+
scale: zoom
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}, [
|
|
45
|
+
dispatch,
|
|
46
|
+
index
|
|
47
|
+
]);
|
|
48
|
+
const handleWheelZoom = useCallback((scale)=>{
|
|
49
|
+
dispatch({
|
|
50
|
+
type: 'SET_ZOOM',
|
|
51
|
+
payload: {
|
|
52
|
+
index,
|
|
53
|
+
scale
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}, [
|
|
57
|
+
dispatch,
|
|
58
|
+
index
|
|
59
|
+
]);
|
|
60
|
+
const handleOrientation = useCallback((displayIndex, orientation)=>{
|
|
61
|
+
dispatch({
|
|
62
|
+
type: 'UPDATE_FILE',
|
|
63
|
+
payload: {
|
|
64
|
+
index: displayIndex,
|
|
65
|
+
file: {
|
|
66
|
+
orientation
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}, [
|
|
71
|
+
dispatch
|
|
72
|
+
]);
|
|
73
|
+
useEffect(()=>{
|
|
74
|
+
const handleKeyDown = (e)=>{
|
|
75
|
+
switch(e.key){
|
|
76
|
+
case 'ArrowLeft':
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
dispatch({
|
|
79
|
+
type: 'PREV_IMAGE'
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
case 'ArrowRight':
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
dispatch({
|
|
85
|
+
type: 'NEXT_IMAGE'
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
case '+':
|
|
89
|
+
case '=':
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
dispatch({
|
|
92
|
+
type: 'ZOOM_IN',
|
|
93
|
+
payload: {
|
|
94
|
+
index,
|
|
95
|
+
step: 0.1
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
break;
|
|
99
|
+
case '-':
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
dispatch({
|
|
102
|
+
type: 'ZOOM_OUT',
|
|
103
|
+
payload: {
|
|
104
|
+
index,
|
|
105
|
+
step: 0.1
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
case 'r':
|
|
110
|
+
case 'R':
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
{
|
|
113
|
+
const file = files[index];
|
|
114
|
+
dispatch({
|
|
115
|
+
type: 'CLEAR_IMAGE',
|
|
116
|
+
payload: {
|
|
117
|
+
index,
|
|
118
|
+
angle: file?.angle ?? 0,
|
|
119
|
+
scale: file?.scale ?? 1
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
127
|
+
return ()=>{
|
|
128
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
129
|
+
};
|
|
130
|
+
}, [
|
|
131
|
+
dispatch,
|
|
132
|
+
index,
|
|
133
|
+
files
|
|
134
|
+
]);
|
|
135
|
+
return /*#__PURE__*/ jsxs("main", {
|
|
136
|
+
className: "flex-image-viewer-content",
|
|
137
|
+
"aria-label": "图片内容",
|
|
138
|
+
children: [
|
|
139
|
+
/*#__PURE__*/ jsx("div", {
|
|
140
|
+
className: "flex-image-viewer-content-tool-left",
|
|
141
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
142
|
+
onClick: ()=>dispatch({
|
|
143
|
+
type: 'PREV_IMAGE'
|
|
144
|
+
}),
|
|
145
|
+
"aria-label": "上一张图片",
|
|
146
|
+
role: "button",
|
|
147
|
+
tabIndex: 0,
|
|
148
|
+
children: /*#__PURE__*/ jsx(Icon, {
|
|
149
|
+
name: "ArrowLeft"
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}),
|
|
153
|
+
/*#__PURE__*/ jsx("div", {
|
|
154
|
+
className: "flex-image-viewer-content-image",
|
|
155
|
+
children: displayIndices.map((displayIndex)=>{
|
|
156
|
+
const isVisible = displayIndex === index;
|
|
157
|
+
return /*#__PURE__*/ jsx(Image, {
|
|
158
|
+
file: files?.[displayIndex],
|
|
159
|
+
scale: imageOptions?.[displayIndex]?.scale,
|
|
160
|
+
angle: imageOptions?.[displayIndex]?.angle,
|
|
161
|
+
isAdapt: imageOptions?.[displayIndex]?.isAdapt,
|
|
162
|
+
onAdapt: handleAdapt,
|
|
163
|
+
wheelZoom: wheelZoom,
|
|
164
|
+
wheelZoomStep: wheelZoomStep,
|
|
165
|
+
onWheelZoom: handleWheelZoom,
|
|
166
|
+
getSrc: getSrc,
|
|
167
|
+
onOrientation: (orientation)=>handleOrientation(displayIndex, orientation),
|
|
168
|
+
disableRightClick: disableRightClick,
|
|
169
|
+
style: {
|
|
170
|
+
display: isVisible ? 'flex' : 'none'
|
|
171
|
+
}
|
|
172
|
+
}, displayIndex);
|
|
173
|
+
})
|
|
174
|
+
}),
|
|
175
|
+
/*#__PURE__*/ jsx("div", {
|
|
176
|
+
className: "flex-image-viewer-content-tool-right",
|
|
177
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
178
|
+
onClick: ()=>dispatch({
|
|
179
|
+
type: 'NEXT_IMAGE'
|
|
180
|
+
}),
|
|
181
|
+
"aria-label": "下一张图片",
|
|
182
|
+
role: "button",
|
|
183
|
+
tabIndex: 0,
|
|
184
|
+
children: /*#__PURE__*/ jsx(Icon, {
|
|
185
|
+
name: "ArrowRight"
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const FlexImageViewer_Content = Content;
|
|
193
|
+
export default FlexImageViewer_Content;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import "react";
|
|
3
|
+
import { BaseActions } from "../../components/index.js";
|
|
4
|
+
import { useViewerDispatch, useViewerState } from "../../context/ViewerContext.js";
|
|
5
|
+
const FOOTER_ITEMS = [
|
|
6
|
+
{
|
|
7
|
+
key: 'thumbnail',
|
|
8
|
+
type: 'icon',
|
|
9
|
+
tooltipKey: 'thumbnail',
|
|
10
|
+
iconName: 'Thumbnail'
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'info',
|
|
14
|
+
type: 'icon',
|
|
15
|
+
tooltipKey: 'info',
|
|
16
|
+
iconName: 'Info'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: 'zoomAdapt',
|
|
20
|
+
type: 'icon',
|
|
21
|
+
tooltipKey: 'zoomAdapt',
|
|
22
|
+
iconName: 'ZoomAdapt'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'zoomSelect',
|
|
26
|
+
type: 'select',
|
|
27
|
+
valueKey: 'zoom'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'zoomOut',
|
|
31
|
+
type: 'icon',
|
|
32
|
+
tooltipKey: 'zoomOut',
|
|
33
|
+
iconName: 'ZoomOut'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'zoomSlider',
|
|
37
|
+
type: 'slider',
|
|
38
|
+
valueKey: 'zoom'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'zoomIn',
|
|
42
|
+
type: 'icon',
|
|
43
|
+
tooltipKey: 'zoomIn',
|
|
44
|
+
iconName: 'ZoomIn'
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
function Actions(props) {
|
|
48
|
+
const { zoom = 1, updateCurrentFile, renderAction } = props;
|
|
49
|
+
const dispatch = useViewerDispatch();
|
|
50
|
+
const state = useViewerState();
|
|
51
|
+
const index = state.currentIndex - 1;
|
|
52
|
+
const handleAction = (key, value, _event)=>{
|
|
53
|
+
switch(key){
|
|
54
|
+
case 'thumbnail':
|
|
55
|
+
dispatch({
|
|
56
|
+
type: 'TOGGLE_THUMBNAIL'
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
case 'info':
|
|
60
|
+
dispatch({
|
|
61
|
+
type: 'TOGGLE_INFO'
|
|
62
|
+
});
|
|
63
|
+
break;
|
|
64
|
+
case 'zoomAdapt':
|
|
65
|
+
dispatch({
|
|
66
|
+
type: 'ADAPT_ZOOM',
|
|
67
|
+
payload: {
|
|
68
|
+
index,
|
|
69
|
+
scale: zoom / 100
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
case 'zoomSelect':
|
|
74
|
+
case 'zoomSlider':
|
|
75
|
+
if (void 0 !== value) dispatch({
|
|
76
|
+
type: 'SET_ZOOM',
|
|
77
|
+
payload: {
|
|
78
|
+
index,
|
|
79
|
+
scale: value / 100
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
case 'zoomOut':
|
|
84
|
+
dispatch({
|
|
85
|
+
type: 'ZOOM_OUT',
|
|
86
|
+
payload: {
|
|
87
|
+
index,
|
|
88
|
+
step: 0.1
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
case 'zoomIn':
|
|
93
|
+
dispatch({
|
|
94
|
+
type: 'ZOOM_IN',
|
|
95
|
+
payload: {
|
|
96
|
+
index,
|
|
97
|
+
step: 0.1
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const context = {
|
|
104
|
+
zoom,
|
|
105
|
+
updateCurrentFile: updateCurrentFile ?? (()=>{})
|
|
106
|
+
};
|
|
107
|
+
return /*#__PURE__*/ jsx(BaseActions, {
|
|
108
|
+
items: FOOTER_ITEMS,
|
|
109
|
+
contextValues: context,
|
|
110
|
+
renderAction: renderAction,
|
|
111
|
+
onAction: handleAction
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const Footer_Actions = Actions;
|
|
115
|
+
export default Footer_Actions;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useViewerState } from "../../context/ViewerContext.js";
|
|
3
|
+
import { Icon } from "../../components/index.js";
|
|
4
|
+
import { shouldSwapDimensions } from "../../utils/exif.js";
|
|
5
|
+
import Actions from "./Actions.js";
|
|
6
|
+
function Footer({ renderAction, updateCurrentFile }) {
|
|
7
|
+
const state = useViewerState();
|
|
8
|
+
const zoom = (state.imageOptions[state.currentIndex - 1]?.scale ?? 1) * 100;
|
|
9
|
+
const files = state.files;
|
|
10
|
+
const current = state.currentIndex;
|
|
11
|
+
const fileData = files[current - 1];
|
|
12
|
+
const width = fileData?.width;
|
|
13
|
+
const height = fileData?.height;
|
|
14
|
+
const sizeArr = shouldSwapDimensions(fileData?.orientation) ? [
|
|
15
|
+
height,
|
|
16
|
+
width
|
|
17
|
+
].filter(Boolean) : [
|
|
18
|
+
width,
|
|
19
|
+
height
|
|
20
|
+
].filter(Boolean);
|
|
21
|
+
return /*#__PURE__*/ jsxs("footer", {
|
|
22
|
+
className: "flex-image-viewer-footer",
|
|
23
|
+
children: [
|
|
24
|
+
/*#__PURE__*/ jsxs("div", {
|
|
25
|
+
className: "flex-image-viewer-footer-title",
|
|
26
|
+
children: [
|
|
27
|
+
2 === sizeArr.length && /*#__PURE__*/ jsxs("div", {
|
|
28
|
+
className: "flex-image-viewer-footer-size",
|
|
29
|
+
children: [
|
|
30
|
+
/*#__PURE__*/ jsx(Icon, {
|
|
31
|
+
name: "Size"
|
|
32
|
+
}),
|
|
33
|
+
sizeArr.join(' x ')
|
|
34
|
+
]
|
|
35
|
+
}),
|
|
36
|
+
!!fileData?.size && /*#__PURE__*/ jsxs("div", {
|
|
37
|
+
className: "flex-image-viewer-footer-memory",
|
|
38
|
+
children: [
|
|
39
|
+
/*#__PURE__*/ jsx(Icon, {
|
|
40
|
+
name: "Save"
|
|
41
|
+
}),
|
|
42
|
+
fileData?.size
|
|
43
|
+
]
|
|
44
|
+
})
|
|
45
|
+
]
|
|
46
|
+
}),
|
|
47
|
+
/*#__PURE__*/ jsx(Actions, {
|
|
48
|
+
zoom: zoom,
|
|
49
|
+
renderAction: renderAction,
|
|
50
|
+
updateCurrentFile: updateCurrentFile
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const FlexImageViewer_Footer = Footer;
|
|
56
|
+
export default FlexImageViewer_Footer;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import "react";
|
|
3
|
+
import { useViewerDispatch, useViewerState } from "../../context/ViewerContext.js";
|
|
4
|
+
import { BaseActions } from "../../components/index.js";
|
|
5
|
+
const HEADER_ITEMS = [
|
|
6
|
+
{
|
|
7
|
+
key: 'rotateLeft',
|
|
8
|
+
type: 'icon',
|
|
9
|
+
tooltipKey: 'rotateLeft',
|
|
10
|
+
iconName: 'RotateLeft',
|
|
11
|
+
tooltipPlacement: 'bottomRight'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
key: 'rotateRight',
|
|
15
|
+
type: 'icon',
|
|
16
|
+
tooltipKey: 'rotateRight',
|
|
17
|
+
iconName: 'RotateRight',
|
|
18
|
+
tooltipPlacement: 'bottomRight'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: 'clear',
|
|
22
|
+
type: 'icon',
|
|
23
|
+
tooltipKey: 'clear',
|
|
24
|
+
iconName: 'Clear',
|
|
25
|
+
tooltipPlacement: 'bottomRight'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: 'close',
|
|
29
|
+
type: 'icon',
|
|
30
|
+
tooltipKey: 'close',
|
|
31
|
+
iconName: 'Close',
|
|
32
|
+
tooltipPlacement: 'bottomRight'
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
function Actions(props) {
|
|
36
|
+
const { rotate = 0, updateCurrentFile, renderAction, onClear, onClose } = props;
|
|
37
|
+
const dispatch = useViewerDispatch();
|
|
38
|
+
const state = useViewerState();
|
|
39
|
+
const index = state.currentIndex - 1;
|
|
40
|
+
const handleAction = (key, _value, event)=>{
|
|
41
|
+
switch(key){
|
|
42
|
+
case 'rotateLeft':
|
|
43
|
+
dispatch({
|
|
44
|
+
type: 'ROTATE_LEFT',
|
|
45
|
+
payload: {
|
|
46
|
+
index
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
break;
|
|
50
|
+
case 'rotateRight':
|
|
51
|
+
dispatch({
|
|
52
|
+
type: 'ROTATE_RIGHT',
|
|
53
|
+
payload: {
|
|
54
|
+
index
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
break;
|
|
58
|
+
case 'clear':
|
|
59
|
+
onClear?.();
|
|
60
|
+
break;
|
|
61
|
+
case 'close':
|
|
62
|
+
onClose?.(event);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const context = {
|
|
67
|
+
rotate,
|
|
68
|
+
updateCurrentFile: updateCurrentFile ?? (()=>{}),
|
|
69
|
+
onClear,
|
|
70
|
+
onClose
|
|
71
|
+
};
|
|
72
|
+
return /*#__PURE__*/ jsx(BaseActions, {
|
|
73
|
+
items: HEADER_ITEMS,
|
|
74
|
+
contextValues: context,
|
|
75
|
+
renderAction: renderAction,
|
|
76
|
+
onAction: handleAction
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const Header_Actions = Actions;
|
|
80
|
+
export default Header_Actions;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "react";
|
|
3
|
+
import { useViewerState } from "../../context/ViewerContext.js";
|
|
4
|
+
import Actions from "./Actions.js";
|
|
5
|
+
function Header({ renderAction, onClose, onClear, updateCurrentFile }) {
|
|
6
|
+
const state = useViewerState();
|
|
7
|
+
const current = state.currentIndex;
|
|
8
|
+
const total = state.filesLength;
|
|
9
|
+
const name = state.files[current - 1]?.name;
|
|
10
|
+
const rotate = state.imageOptions[current - 1]?.angle ?? 0;
|
|
11
|
+
return /*#__PURE__*/ jsxs("header", {
|
|
12
|
+
className: "flex-image-viewer-header",
|
|
13
|
+
children: [
|
|
14
|
+
/*#__PURE__*/ jsxs("div", {
|
|
15
|
+
className: "flex-image-viewer-header-title",
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ jsxs("div", {
|
|
18
|
+
className: "flex-image-viewer-header-count",
|
|
19
|
+
children: [
|
|
20
|
+
current,
|
|
21
|
+
"/",
|
|
22
|
+
total
|
|
23
|
+
]
|
|
24
|
+
}),
|
|
25
|
+
/*#__PURE__*/ jsx("div", {
|
|
26
|
+
className: "flex-image-viewer-header-name",
|
|
27
|
+
children: name
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
}),
|
|
31
|
+
/*#__PURE__*/ jsx(Actions, {
|
|
32
|
+
rotate: rotate,
|
|
33
|
+
renderAction: renderAction,
|
|
34
|
+
onClose: onClose,
|
|
35
|
+
onClear: onClear,
|
|
36
|
+
updateCurrentFile: updateCurrentFile
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const FlexImageViewer_Header = Header;
|
|
42
|
+
export default FlexImageViewer_Header;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCreation } from "ahooks";
|
|
3
|
+
import { useViewerDispatch, useViewerState } from "../../context/ViewerContext.js";
|
|
4
|
+
import { Icon } from "../../components/index.js";
|
|
5
|
+
function Info(props) {
|
|
6
|
+
const { renderInfo } = props;
|
|
7
|
+
const state = useViewerState();
|
|
8
|
+
const dispatch = useViewerDispatch();
|
|
9
|
+
const visible = state.infoVisible;
|
|
10
|
+
const files = state.files;
|
|
11
|
+
const current = state.currentIndex;
|
|
12
|
+
const fileData = files[current];
|
|
13
|
+
const content = useCreation(()=>renderInfo ? renderInfo(fileData) : /*#__PURE__*/ jsx("ul", {
|
|
14
|
+
children: fileData?.infoData?.map((item, index)=>/*#__PURE__*/ jsxs("li", {
|
|
15
|
+
children: [
|
|
16
|
+
/*#__PURE__*/ jsxs("span", {
|
|
17
|
+
children: [
|
|
18
|
+
item.label,
|
|
19
|
+
":"
|
|
20
|
+
]
|
|
21
|
+
}),
|
|
22
|
+
/*#__PURE__*/ jsx("p", {
|
|
23
|
+
children: item.value
|
|
24
|
+
})
|
|
25
|
+
]
|
|
26
|
+
}, index))
|
|
27
|
+
}), [
|
|
28
|
+
fileData,
|
|
29
|
+
renderInfo
|
|
30
|
+
]);
|
|
31
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
32
|
+
className: `flex-image-viewer-info ${visible ? 'visible' : ''}`,
|
|
33
|
+
children: [
|
|
34
|
+
/*#__PURE__*/ jsx("div", {
|
|
35
|
+
className: "flex-image-viewer-info-tool",
|
|
36
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
37
|
+
"aria-label": "切换信息面板",
|
|
38
|
+
role: "button",
|
|
39
|
+
tabIndex: 0,
|
|
40
|
+
onClick: ()=>dispatch({
|
|
41
|
+
type: 'TOGGLE_INFO'
|
|
42
|
+
}),
|
|
43
|
+
children: /*#__PURE__*/ jsx(Icon, {
|
|
44
|
+
name: "CaretRight"
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
}),
|
|
48
|
+
content
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export default Info;
|