@lobehub/ui 5.25.0 → 5.27.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/es/Image/Image.mjs +138 -37
- package/es/Image/Image.mjs.map +1 -1
- package/es/Image/PreviewGroup.d.mts +1 -0
- package/es/Image/PreviewGroup.mjs +33 -10
- package/es/Image/PreviewGroup.mjs.map +1 -1
- package/es/Image/index.d.mts +2 -2
- package/es/Image/index.mjs +1 -1
- package/es/Image/style.mjs +126 -71
- package/es/Image/style.mjs.map +1 -1
- package/es/Image/type.d.mts +14 -17
- package/es/Image/viewer/GalleryNav.mjs +44 -0
- package/es/Image/viewer/GalleryNav.mjs.map +1 -0
- package/es/Image/viewer/ImageViewer.mjs +275 -0
- package/es/Image/viewer/ImageViewer.mjs.map +1 -0
- package/es/Image/viewer/PreviewOutlet.mjs +19 -0
- package/es/Image/viewer/PreviewOutlet.mjs.map +1 -0
- package/es/Image/viewer/Toolbar.mjs +184 -0
- package/es/Image/viewer/Toolbar.mjs.map +1 -0
- package/es/Image/viewer/ViewerChrome.mjs +54 -0
- package/es/Image/viewer/ViewerChrome.mjs.map +1 -0
- package/es/Image/viewer/geometry.mjs +85 -0
- package/es/Image/viewer/geometry.mjs.map +1 -0
- package/es/Image/viewer/registry.d.mts +2 -0
- package/es/Image/viewer/registry.mjs +52 -0
- package/es/Image/viewer/registry.mjs.map +1 -0
- package/es/Image/viewer/useChromeIdle.mjs +72 -0
- package/es/Image/viewer/useChromeIdle.mjs.map +1 -0
- package/es/Image/viewer/useFinalFocus.mjs +10 -0
- package/es/Image/viewer/useFinalFocus.mjs.map +1 -0
- package/es/Image/viewer/useFlipTransition.mjs +321 -0
- package/es/Image/viewer/useFlipTransition.mjs.map +1 -0
- package/es/Image/viewer/useGalleryNav.mjs +89 -0
- package/es/Image/viewer/useGalleryNav.mjs.map +1 -0
- package/es/Image/viewer/useRefitTransition.mjs +47 -0
- package/es/Image/viewer/useRefitTransition.mjs.map +1 -0
- package/es/Image/viewer/useViewerGestures.mjs +148 -0
- package/es/Image/viewer/useViewerGestures.mjs.map +1 -0
- package/es/Image/viewer/useZoomPan.mjs +346 -0
- package/es/Image/viewer/useZoomPan.mjs.map +1 -0
- package/es/base-ui/Toast/hostGuard.mjs +26 -0
- package/es/base-ui/Toast/hostGuard.mjs.map +1 -0
- package/es/base-ui/Toast/imperative.mjs +69 -31
- package/es/base-ui/Toast/imperative.mjs.map +1 -1
- package/es/base-ui/Toast/pendingQueue.mjs +30 -0
- package/es/base-ui/Toast/pendingQueue.mjs.map +1 -0
- package/es/base-ui/Toast/type.d.mts +7 -0
- package/es/base-ui/index.mjs +1 -1
- package/es/i18n/resources/en/image.d.mts +5 -0
- package/es/i18n/resources/en/image.mjs +6 -1
- package/es/i18n/resources/en/image.mjs.map +1 -1
- package/es/i18n/resources/zhCn/image.d.mts +5 -0
- package/es/i18n/resources/zhCn/image.mjs +6 -1
- package/es/i18n/resources/zhCn/image.mjs.map +1 -1
- package/es/index.mjs +8 -8
- package/package.json +1 -1
- package/es/Image/components/Preview.mjs +0 -23
- package/es/Image/components/Preview.mjs.map +0 -1
- package/es/Image/components/Toolbar.mjs +0 -129
- package/es/Image/components/Toolbar.mjs.map +0 -1
- package/es/Image/components/usePreview.mjs +0 -58
- package/es/Image/components/usePreview.mjs.map +0 -1
- package/es/Image/components/usePreviewGroup.mjs +0 -57
- package/es/Image/components/usePreviewGroup.mjs.map +0 -1
package/es/Image/type.d.mts
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
import { CSSProperties, ReactNode, Ref } from "react";
|
|
2
|
-
import { ImageProps } from "antd";
|
|
3
|
-
import { PreviewConfig } from "antd/es/image/index.js";
|
|
4
|
-
import { GroupPreviewConfig } from "antd/es/image/PreviewGroup.js";
|
|
1
|
+
import { CSSProperties, ComponentProps, ReactNode, Ref } from "react";
|
|
5
2
|
//#region src/Image/type.d.ts
|
|
6
|
-
interface
|
|
3
|
+
interface ImagePreviewOptions {
|
|
4
|
+
maxScale?: number;
|
|
5
|
+
onOpenChange?: (open: boolean) => void;
|
|
6
|
+
src?: string;
|
|
7
7
|
toolbarAddon?: ReactNode;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
children?: ReactNode;
|
|
11
|
-
enable?: boolean;
|
|
12
|
-
items?: string[];
|
|
13
|
-
preview?: boolean | PreviewGroupPreviewOptions;
|
|
14
|
-
}
|
|
15
|
-
interface ImagePreviewOptions extends PreviewConfig {
|
|
16
|
-
toolbarAddon?: ReactNode;
|
|
17
|
-
}
|
|
18
|
-
interface ImageProps$1 extends Omit<ImageProps, 'preview'> {
|
|
9
|
+
type ImageProps = Omit<ComponentProps<'img'>, 'width' | 'height'> & {
|
|
19
10
|
actions?: ReactNode;
|
|
20
11
|
alwaysShowActions?: boolean;
|
|
21
12
|
classNames?: {
|
|
22
13
|
image?: string;
|
|
23
14
|
wrapper?: string;
|
|
24
15
|
};
|
|
16
|
+
height?: number | string;
|
|
25
17
|
isLoading?: boolean;
|
|
26
18
|
maxHeight?: number | string;
|
|
27
19
|
maxWidth?: number | string;
|
|
@@ -35,9 +27,14 @@ interface ImageProps$1 extends Omit<ImageProps, 'preview'> {
|
|
|
35
27
|
image?: CSSProperties;
|
|
36
28
|
wrapper?: CSSProperties;
|
|
37
29
|
};
|
|
38
|
-
toolbarAddon?: ReactNode;
|
|
39
30
|
variant?: 'borderless' | 'filled' | 'outlined';
|
|
31
|
+
width?: number | string;
|
|
32
|
+
};
|
|
33
|
+
interface PreviewGroupProps {
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
enable?: boolean;
|
|
36
|
+
preview?: boolean | ImagePreviewOptions;
|
|
40
37
|
}
|
|
41
38
|
//#endregion
|
|
42
|
-
export { ImagePreviewOptions, ImageProps
|
|
39
|
+
export { ImagePreviewOptions, ImageProps, PreviewGroupProps };
|
|
43
40
|
//# sourceMappingURL=type.d.mts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Center from "../../Flex/Center.mjs";
|
|
3
|
+
import ActionIcon from "../../ActionIcon/ActionIcon.mjs";
|
|
4
|
+
import { useTranslation } from "../../i18n/useTranslation.mjs";
|
|
5
|
+
import { styles } from "../style.mjs";
|
|
6
|
+
import image_default from "../../i18n/resources/en/image.mjs";
|
|
7
|
+
import { memo } from "react";
|
|
8
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { cx } from "antd-style";
|
|
10
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
11
|
+
//#region src/Image/viewer/GalleryNav.tsx
|
|
12
|
+
const GalleryNav = memo(({ current, hasNext, hasPrev, next, prev, total }) => {
|
|
13
|
+
const { t } = useTranslation(image_default);
|
|
14
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
15
|
+
hasPrev && /* @__PURE__ */ jsx(ActionIcon, {
|
|
16
|
+
className: cx(styles.viewerNavButton, styles.viewerNavPrev),
|
|
17
|
+
icon: ChevronLeft,
|
|
18
|
+
size: "large",
|
|
19
|
+
title: t("image.prev"),
|
|
20
|
+
onClick: prev
|
|
21
|
+
}),
|
|
22
|
+
hasNext && /* @__PURE__ */ jsx(ActionIcon, {
|
|
23
|
+
className: cx(styles.viewerNavButton, styles.viewerNavNext),
|
|
24
|
+
icon: ChevronRight,
|
|
25
|
+
size: "large",
|
|
26
|
+
title: t("image.next"),
|
|
27
|
+
onClick: next
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ jsxs(Center, {
|
|
30
|
+
horizontal: true,
|
|
31
|
+
className: styles.viewerCounter,
|
|
32
|
+
children: [
|
|
33
|
+
current + 1,
|
|
34
|
+
" / ",
|
|
35
|
+
total
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
] });
|
|
39
|
+
});
|
|
40
|
+
GalleryNav.displayName = "GalleryNav";
|
|
41
|
+
//#endregion
|
|
42
|
+
export { GalleryNav as default };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=GalleryNav.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GalleryNav.mjs","names":["imageMessages"],"sources":["../../../src/Image/viewer/GalleryNav.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { memo } from 'react';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Center } from '@/Flex';\nimport imageMessages from '@/i18n/resources/en/image';\nimport { useTranslation } from '@/i18n/useTranslation';\n\nimport { styles } from '../style';\n\nexport interface GalleryNavProps {\n current: number;\n hasNext: boolean;\n hasPrev: boolean;\n next: () => void;\n prev: () => void;\n total: number;\n}\n\nconst GalleryNav = memo<GalleryNavProps>(({ current, hasNext, hasPrev, next, prev, total }) => {\n const { t } = useTranslation(imageMessages);\n\n return (\n <>\n {hasPrev && (\n <ActionIcon\n className={cx(styles.viewerNavButton, styles.viewerNavPrev)}\n icon={ChevronLeft}\n size={'large'}\n title={t('image.prev')}\n onClick={prev}\n />\n )}\n {hasNext && (\n <ActionIcon\n className={cx(styles.viewerNavButton, styles.viewerNavNext)}\n icon={ChevronRight}\n size={'large'}\n title={t('image.next')}\n onClick={next}\n />\n )}\n <Center horizontal className={styles.viewerCounter}>\n {current + 1} / {total}\n </Center>\n </>\n );\n});\n\nGalleryNav.displayName = 'GalleryNav';\n\nexport default GalleryNav;\n"],"mappings":";;;;;;;;;;;AAsBA,MAAM,aAAa,MAAuB,EAAE,SAAS,SAAS,SAAS,MAAM,MAAM,YAAY;CAC7F,MAAM,EAAE,MAAM,eAAeA,aAAa;CAE1C,OACE,qBAAA,YAAA,EAAA,UAAA;EACG,WACC,oBAAC,YAAD;GACE,WAAW,GAAG,OAAO,iBAAiB,OAAO,aAAa;GAC1D,MAAM;GACN,MAAM;GACN,OAAO,EAAE,YAAY;GACrB,SAAS;EACV,CAAA;EAEF,WACC,oBAAC,YAAD;GACE,WAAW,GAAG,OAAO,iBAAiB,OAAO,aAAa;GAC1D,MAAM;GACN,MAAM;GACN,OAAO,EAAE,YAAY;GACrB,SAAS;EACV,CAAA;EAEH,qBAAC,QAAD;GAAQ,YAAA;GAAW,WAAW,OAAO;GAArC,UAAA;IACG,UAAU;IAAE;IAAI;GACX;;CACR,EAAA,CAAA;AAEN,CAAC;AAED,WAAW,cAAc"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { MotionComponent } from "../../MotionProvider/index.mjs";
|
|
3
|
+
import { useAppElement } from "../../ThemeProvider/AppElementContext.mjs";
|
|
4
|
+
import { useLayerZIndex } from "../../base-ui/zIndex/useLayerZIndex.mjs";
|
|
5
|
+
import { styles } from "../style.mjs";
|
|
6
|
+
import { ToastHost } from "../../base-ui/Toast/imperative.mjs";
|
|
7
|
+
import { computeFit, unrotatedRect } from "./geometry.mjs";
|
|
8
|
+
import { beginClosePreview, endClosePreview } from "./registry.mjs";
|
|
9
|
+
import { useFinalFocus } from "./useFinalFocus.mjs";
|
|
10
|
+
import { useFlipTransition } from "./useFlipTransition.mjs";
|
|
11
|
+
import { useViewerGestures } from "./useViewerGestures.mjs";
|
|
12
|
+
import { readNatural, useGalleryNav } from "./useGalleryNav.mjs";
|
|
13
|
+
import { useRefitTransition } from "./useRefitTransition.mjs";
|
|
14
|
+
import { useZoomPan } from "./useZoomPan.mjs";
|
|
15
|
+
import ViewerChrome from "./ViewerChrome.mjs";
|
|
16
|
+
import { memo, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
17
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { useMergeRefs } from "react-merge-refs";
|
|
19
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
20
|
+
//#region src/Image/viewer/ImageViewer.tsx
|
|
21
|
+
const readViewport = () => ({
|
|
22
|
+
height: window.innerHeight,
|
|
23
|
+
width: window.innerWidth
|
|
24
|
+
});
|
|
25
|
+
const prefersReducedMotion = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
|
26
|
+
const ImageViewer = memo(({ entries, index, openerFocusElement, token }) => {
|
|
27
|
+
const openerEntry = entries[index];
|
|
28
|
+
const appElement = useAppElement();
|
|
29
|
+
const motionComponent = use(MotionComponent);
|
|
30
|
+
const [animated] = useState(() => !!motionComponent && !prefersReducedMotion());
|
|
31
|
+
const { ref: layerRef, zIndex } = useLayerZIndex("modal");
|
|
32
|
+
const [currentIndex, setCurrentIndex] = useState(index);
|
|
33
|
+
const currentEntry = entries[currentIndex];
|
|
34
|
+
const currentEntryRef = useRef(currentEntry);
|
|
35
|
+
currentEntryRef.current = currentEntry;
|
|
36
|
+
const [source, setSource] = useState(openerEntry.src);
|
|
37
|
+
const [natural, setNatural] = useState(() => readNatural(openerEntry.element));
|
|
38
|
+
const [viewport, setViewport] = useState(readViewport);
|
|
39
|
+
const closeRef = useRef(null);
|
|
40
|
+
const requestClose = useCallback(() => closeRef.current?.(), []);
|
|
41
|
+
const isClosingIndirectRef = useRef(() => false);
|
|
42
|
+
const isClosingIndirect = useCallback(() => isClosingIndirectRef.current(), []);
|
|
43
|
+
const fillViewport = Boolean(currentEntry.previewSrc && currentEntry.previewSrc !== currentEntry.src);
|
|
44
|
+
const { canZoomIn, canZoomOut, dragBy, dragEnd, escIntent, flipHorizontal, flipVertical, flipX, flipY, handleDoubleClick, handleWheel, isClean, isZoomed, reset, resetCloseTracking, rotate, rotateLeft, rotateRight, rotation, scale, setNatural: syncZoomNatural, setViewport: syncZoomViewport, x, y, zoomIn, zoomOut } = useZoomPan({
|
|
45
|
+
fillViewport,
|
|
46
|
+
isClosing: isClosingIndirect,
|
|
47
|
+
maxScale: currentEntry.options.maxScale,
|
|
48
|
+
natural,
|
|
49
|
+
onCloseRequest: requestClose,
|
|
50
|
+
viewport
|
|
51
|
+
});
|
|
52
|
+
const fitRect = useMemo(() => computeFit(natural, viewport, rotation, fillViewport), [
|
|
53
|
+
natural,
|
|
54
|
+
viewport,
|
|
55
|
+
rotation,
|
|
56
|
+
fillViewport
|
|
57
|
+
]);
|
|
58
|
+
const fitRectRef = useRef(fitRect);
|
|
59
|
+
fitRectRef.current = fitRect;
|
|
60
|
+
const getFitRect = useCallback(() => fitRectRef.current, []);
|
|
61
|
+
const imageRect = useMemo(() => unrotatedRect(fitRect, rotation), [fitRect, rotation]);
|
|
62
|
+
const viewportRef = useRef(viewport);
|
|
63
|
+
viewportRef.current = viewport;
|
|
64
|
+
const getViewportWidth = useCallback(() => viewportRef.current.width, []);
|
|
65
|
+
const getCloseSource = useCallback(() => currentEntryRef.current.element, []);
|
|
66
|
+
const transform = useMemo(() => ({
|
|
67
|
+
flipX,
|
|
68
|
+
flipY,
|
|
69
|
+
rotate,
|
|
70
|
+
scale,
|
|
71
|
+
x,
|
|
72
|
+
y
|
|
73
|
+
}), [
|
|
74
|
+
flipX,
|
|
75
|
+
flipY,
|
|
76
|
+
rotate,
|
|
77
|
+
scale,
|
|
78
|
+
x,
|
|
79
|
+
y
|
|
80
|
+
]);
|
|
81
|
+
const handleClosed = useCallback(() => endClosePreview(token), [token]);
|
|
82
|
+
const { backdropRef, chromeRef, close, imageRef: flipImageRef, isClosing, isTransitioning, switchTo } = useFlipTransition({
|
|
83
|
+
animated,
|
|
84
|
+
getCloseSource,
|
|
85
|
+
getFitRect,
|
|
86
|
+
getViewportWidth,
|
|
87
|
+
onClosed: handleClosed,
|
|
88
|
+
source: openerEntry.element,
|
|
89
|
+
transform
|
|
90
|
+
});
|
|
91
|
+
isClosingIndirectRef.current = isClosing;
|
|
92
|
+
useRefitTransition({
|
|
93
|
+
animated,
|
|
94
|
+
fillViewport,
|
|
95
|
+
isTransitioning,
|
|
96
|
+
natural,
|
|
97
|
+
rotation,
|
|
98
|
+
scale,
|
|
99
|
+
viewport,
|
|
100
|
+
x,
|
|
101
|
+
y
|
|
102
|
+
});
|
|
103
|
+
const finalFocus = useFinalFocus(openerFocusElement);
|
|
104
|
+
const isCleanRef = useRef(isClean);
|
|
105
|
+
isCleanRef.current = isClean;
|
|
106
|
+
const handleClose = useCallback(() => {
|
|
107
|
+
beginClosePreview(token);
|
|
108
|
+
close({ fade: !isCleanRef.current });
|
|
109
|
+
}, [close, token]);
|
|
110
|
+
closeRef.current = handleClose;
|
|
111
|
+
const handleDialogOpenChange = useCallback((open, eventDetails) => {
|
|
112
|
+
if (open) return;
|
|
113
|
+
if (isClosing()) return;
|
|
114
|
+
if (eventDetails.reason === "escape-key" && escIntent() === "reset") {
|
|
115
|
+
eventDetails.cancel();
|
|
116
|
+
reset();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
handleClose();
|
|
120
|
+
}, [
|
|
121
|
+
escIntent,
|
|
122
|
+
handleClose,
|
|
123
|
+
isClosing,
|
|
124
|
+
reset
|
|
125
|
+
]);
|
|
126
|
+
const gestures = useViewerGestures({
|
|
127
|
+
dragBy,
|
|
128
|
+
dragEnd,
|
|
129
|
+
handleDoubleClick,
|
|
130
|
+
handleWheel,
|
|
131
|
+
isClean,
|
|
132
|
+
isZoomed,
|
|
133
|
+
onClose: handleClose,
|
|
134
|
+
reset,
|
|
135
|
+
zoomIn,
|
|
136
|
+
zoomOut
|
|
137
|
+
});
|
|
138
|
+
const { hasNext, hasPrev, next, prev } = useGalleryNav({
|
|
139
|
+
cancelPendingClose: gestures.cancelPendingClose,
|
|
140
|
+
currentIndex,
|
|
141
|
+
entries,
|
|
142
|
+
flipX,
|
|
143
|
+
flipY,
|
|
144
|
+
resetCloseTracking,
|
|
145
|
+
rotate,
|
|
146
|
+
scale,
|
|
147
|
+
setCurrentIndex,
|
|
148
|
+
setNatural,
|
|
149
|
+
setSource,
|
|
150
|
+
switchTo,
|
|
151
|
+
syncZoomNatural,
|
|
152
|
+
x,
|
|
153
|
+
y
|
|
154
|
+
});
|
|
155
|
+
const popupNodeRef = useRef(null);
|
|
156
|
+
const popupRef = useMergeRefs([
|
|
157
|
+
layerRef,
|
|
158
|
+
gestures.popupRef,
|
|
159
|
+
popupNodeRef
|
|
160
|
+
]);
|
|
161
|
+
const imageRef = useMergeRefs([flipImageRef, gestures.imageRef]);
|
|
162
|
+
const handleLoad = useCallback((event) => {
|
|
163
|
+
const loaded = event.currentTarget;
|
|
164
|
+
if (!loaded.naturalWidth || !loaded.naturalHeight) return;
|
|
165
|
+
const next = {
|
|
166
|
+
height: loaded.naturalHeight,
|
|
167
|
+
width: loaded.naturalWidth
|
|
168
|
+
};
|
|
169
|
+
setNatural((prev) => prev.width === next.width && prev.height === next.height ? prev : next);
|
|
170
|
+
syncZoomNatural(next);
|
|
171
|
+
}, [syncZoomNatural]);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
const handleResize = () => {
|
|
174
|
+
const next = readViewport();
|
|
175
|
+
setViewport(next);
|
|
176
|
+
syncZoomViewport(next);
|
|
177
|
+
};
|
|
178
|
+
window.addEventListener("resize", handleResize);
|
|
179
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
180
|
+
}, [syncZoomViewport]);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const { previewSrc, src } = currentEntry;
|
|
183
|
+
if (!previewSrc || previewSrc === src) return;
|
|
184
|
+
let cancelled = false;
|
|
185
|
+
const loader = new window.Image();
|
|
186
|
+
const handleLoaded = () => {
|
|
187
|
+
if (!cancelled) setSource(previewSrc);
|
|
188
|
+
};
|
|
189
|
+
loader.addEventListener("load", handleLoaded);
|
|
190
|
+
loader.src = previewSrc;
|
|
191
|
+
return () => {
|
|
192
|
+
cancelled = true;
|
|
193
|
+
loader.removeEventListener("load", handleLoaded);
|
|
194
|
+
};
|
|
195
|
+
}, [currentEntry]);
|
|
196
|
+
useEffect(() => () => {
|
|
197
|
+
if (!openerEntry.element.isConnected) endClosePreview(token);
|
|
198
|
+
}, [openerEntry, token]);
|
|
199
|
+
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
200
|
+
modal: true,
|
|
201
|
+
open: true,
|
|
202
|
+
onOpenChange: handleDialogOpenChange,
|
|
203
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
204
|
+
container: appElement ?? void 0,
|
|
205
|
+
children: [
|
|
206
|
+
/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
207
|
+
className: styles.viewerBackdrop,
|
|
208
|
+
ref: backdropRef,
|
|
209
|
+
style: zIndex === void 0 ? void 0 : { zIndex },
|
|
210
|
+
onClick: gestures.onSurfaceClick
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
213
|
+
"aria-label": currentEntry.element.alt || void 0,
|
|
214
|
+
className: styles.viewerPopup,
|
|
215
|
+
finalFocus,
|
|
216
|
+
initialFocus: popupNodeRef,
|
|
217
|
+
ref: popupRef,
|
|
218
|
+
style: zIndex === void 0 ? void 0 : { zIndex: zIndex + 1 },
|
|
219
|
+
tabIndex: -1,
|
|
220
|
+
onClick: gestures.onSurfaceClick,
|
|
221
|
+
onPointerCancel: gestures.onPointerFinish,
|
|
222
|
+
onPointerDown: gestures.onPointerDown,
|
|
223
|
+
onPointerMove: gestures.onPointerMove,
|
|
224
|
+
onPointerUp: gestures.onPointerFinish,
|
|
225
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
226
|
+
alt: currentEntry.element.alt,
|
|
227
|
+
className: styles.viewerImage,
|
|
228
|
+
ref: imageRef,
|
|
229
|
+
src: source,
|
|
230
|
+
style: {
|
|
231
|
+
cursor: gestures.cursor,
|
|
232
|
+
height: imageRect.height,
|
|
233
|
+
left: imageRect.x,
|
|
234
|
+
top: imageRect.y,
|
|
235
|
+
width: imageRect.width
|
|
236
|
+
},
|
|
237
|
+
onClick: gestures.onImageClick,
|
|
238
|
+
onDoubleClick: gestures.onImageDoubleClick,
|
|
239
|
+
onLoad: handleLoad
|
|
240
|
+
}), /* @__PURE__ */ jsx(ViewerChrome, {
|
|
241
|
+
canZoomIn,
|
|
242
|
+
canZoomOut,
|
|
243
|
+
chromeRef,
|
|
244
|
+
current: currentIndex,
|
|
245
|
+
fitRect,
|
|
246
|
+
flipHorizontal,
|
|
247
|
+
flipVertical,
|
|
248
|
+
hasNext,
|
|
249
|
+
hasPrev,
|
|
250
|
+
natural,
|
|
251
|
+
next,
|
|
252
|
+
prev,
|
|
253
|
+
reset,
|
|
254
|
+
rotateLeft,
|
|
255
|
+
rotateRight,
|
|
256
|
+
rotation,
|
|
257
|
+
scale,
|
|
258
|
+
source,
|
|
259
|
+
toolbarAddon: currentEntry.options.toolbarAddon,
|
|
260
|
+
total: entries.length,
|
|
261
|
+
zoomIn,
|
|
262
|
+
zoomOut,
|
|
263
|
+
onClose: handleClose
|
|
264
|
+
})]
|
|
265
|
+
}),
|
|
266
|
+
/* @__PURE__ */ jsx(ToastHost, { root: appElement ?? void 0 })
|
|
267
|
+
]
|
|
268
|
+
})
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
ImageViewer.displayName = "ImageViewer";
|
|
272
|
+
//#endregion
|
|
273
|
+
export { ImageViewer as default };
|
|
274
|
+
|
|
275
|
+
//# sourceMappingURL=ImageViewer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageViewer.mjs","names":[],"sources":["../../../src/Image/viewer/ImageViewer.tsx"],"sourcesContent":["'use client';\n\nimport { Dialog, type DialogRootProps } from '@base-ui/react/dialog';\nimport {\n memo,\n type SyntheticEvent,\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useMergeRefs } from 'react-merge-refs';\n\nimport { ToastHost } from '@/base-ui/Toast';\nimport { useLayerZIndex } from '@/base-ui/zIndex';\nimport { MotionComponent } from '@/MotionProvider';\nimport { useAppElement } from '@/ThemeProvider';\n\nimport { styles } from '../style';\nimport { computeFit, type Size, unrotatedRect } from './geometry';\nimport { beginClosePreview, endClosePreview, type PreviewEntry } from './registry';\nimport { useFinalFocus } from './useFinalFocus';\nimport { useFlipTransition } from './useFlipTransition';\nimport { readNatural, useGalleryNav } from './useGalleryNav';\nimport { useRefitTransition } from './useRefitTransition';\nimport { useViewerGestures } from './useViewerGestures';\nimport { useZoomPan } from './useZoomPan';\nimport ViewerChrome from './ViewerChrome';\n\nexport interface ImageViewerProps {\n entries: PreviewEntry[];\n index: number;\n openerFocusElement: HTMLElement | null;\n token: number;\n}\n\nconst readViewport = (): Size => ({ height: window.innerHeight, width: window.innerWidth });\n\nconst prefersReducedMotion = () =>\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n\nconst ImageViewer = memo<ImageViewerProps>(({ entries, index, openerFocusElement, token }) => {\n const openerEntry = entries[index];\n const appElement = useAppElement();\n const motionComponent = use(MotionComponent);\n const [animated] = useState(() => !!motionComponent && !prefersReducedMotion());\n\n const { ref: layerRef, zIndex } = useLayerZIndex<HTMLDivElement>('modal');\n\n const [currentIndex, setCurrentIndex] = useState(index);\n const currentEntry = entries[currentIndex];\n const currentEntryRef = useRef(currentEntry);\n currentEntryRef.current = currentEntry;\n\n const [source, setSource] = useState(openerEntry.src);\n const [natural, setNatural] = useState<Size>(() => readNatural(openerEntry.element));\n const [viewport, setViewport] = useState<Size>(readViewport);\n\n const closeRef = useRef<(() => void) | null>(null);\n const requestClose = useCallback(() => closeRef.current?.(), []);\n\n // useZoomPan needs to gate its wheel/toolbar entry points on close-in-flight,\n // but that state only exists once useFlipTransition (below) has run — and it\n // needs useZoomPan's own transform values first. Break the cycle with a\n // stable indirection: the ref is repointed at the real isClosing once it's\n // available, written directly during render like the other *Ref fields here.\n const isClosingIndirectRef = useRef<() => boolean>(() => false);\n const isClosingIndirect = useCallback(() => isClosingIndirectRef.current(), []);\n\n const fillViewport = Boolean(\n currentEntry.previewSrc && currentEntry.previewSrc !== currentEntry.src,\n );\n\n const {\n canZoomIn,\n canZoomOut,\n dragBy,\n dragEnd,\n escIntent,\n flipHorizontal,\n flipVertical,\n flipX,\n flipY,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n reset,\n resetCloseTracking,\n rotate,\n rotateLeft,\n rotateRight,\n rotation,\n scale,\n setNatural: syncZoomNatural,\n setViewport: syncZoomViewport,\n x,\n y,\n zoomIn,\n zoomOut,\n } = useZoomPan({\n fillViewport,\n isClosing: isClosingIndirect,\n maxScale: currentEntry.options.maxScale,\n natural,\n onCloseRequest: requestClose,\n viewport,\n });\n\n const fitRect = useMemo(\n () => computeFit(natural, viewport, rotation, fillViewport),\n [natural, viewport, rotation, fillViewport],\n );\n const fitRectRef = useRef(fitRect);\n fitRectRef.current = fitRect;\n const getFitRect = useCallback(() => fitRectRef.current, []);\n\n const imageRect = useMemo(() => unrotatedRect(fitRect, rotation), [fitRect, rotation]);\n\n const viewportRef = useRef(viewport);\n viewportRef.current = viewport;\n const getViewportWidth = useCallback(() => viewportRef.current.width, []);\n\n const getCloseSource = useCallback(() => currentEntryRef.current.element, []);\n\n const transform = useMemo(\n () => ({ flipX, flipY, rotate, scale, x, y }),\n [flipX, flipY, rotate, scale, x, y],\n );\n const handleClosed = useCallback(() => endClosePreview(token), [token]);\n\n const {\n backdropRef,\n chromeRef,\n close,\n imageRef: flipImageRef,\n isClosing,\n isTransitioning,\n switchTo,\n } = useFlipTransition({\n animated,\n getCloseSource,\n getFitRect,\n getViewportWidth,\n onClosed: handleClosed,\n source: openerEntry.element,\n transform,\n });\n isClosingIndirectRef.current = isClosing;\n\n useRefitTransition({\n animated,\n fillViewport,\n isTransitioning,\n natural,\n rotation,\n scale,\n viewport,\n x,\n y,\n });\n const finalFocus = useFinalFocus(openerFocusElement);\n\n const isCleanRef = useRef(isClean);\n isCleanRef.current = isClean;\n\n const handleClose = useCallback(() => {\n beginClosePreview(token);\n close({ fade: !isCleanRef.current });\n }, [close, token]);\n closeRef.current = handleClose;\n\n const handleDialogOpenChange = useCallback<NonNullable<DialogRootProps['onOpenChange']>>(\n (open, eventDetails) => {\n if (open) return;\n // Base UI fires this on every Esc regardless of whether we're already\n // mid-close (our Dialog.Root's open prop never actually flips to\n // false), so without this guard a second Esc during the close window\n // reads escIntent() off the close spring's mid-flight scale, decides\n // 'reset', and cancels the close animation out from under itself.\n if (isClosing()) return;\n if (eventDetails.reason === 'escape-key' && escIntent() === 'reset') {\n eventDetails.cancel();\n reset();\n return;\n }\n handleClose();\n },\n [escIntent, handleClose, isClosing, reset],\n );\n\n const gestures = useViewerGestures({\n dragBy,\n dragEnd,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n onClose: handleClose,\n reset,\n zoomIn,\n zoomOut,\n });\n\n const { hasNext, hasPrev, next, prev } = useGalleryNav({\n cancelPendingClose: gestures.cancelPendingClose,\n currentIndex,\n entries,\n flipX,\n flipY,\n resetCloseTracking,\n rotate,\n scale,\n setCurrentIndex,\n setNatural,\n setSource,\n switchTo,\n syncZoomNatural,\n x,\n y,\n });\n\n const popupNodeRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useMergeRefs<HTMLDivElement>([layerRef, gestures.popupRef, popupNodeRef]);\n const imageRef = useMergeRefs<HTMLImageElement>([flipImageRef, gestures.imageRef]);\n\n const handleLoad = useCallback(\n (event: SyntheticEvent<HTMLImageElement>) => {\n const loaded = event.currentTarget;\n if (!loaded.naturalWidth || !loaded.naturalHeight) return;\n const next = { height: loaded.naturalHeight, width: loaded.naturalWidth };\n setNatural((prev) =>\n prev.width === next.width && prev.height === next.height ? prev : next,\n );\n syncZoomNatural(next);\n },\n [syncZoomNatural],\n );\n\n useEffect(() => {\n const handleResize = () => {\n const next = readViewport();\n setViewport(next);\n syncZoomViewport(next);\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [syncZoomViewport]);\n\n useEffect(() => {\n const { previewSrc, src } = currentEntry;\n if (!previewSrc || previewSrc === src) return;\n let cancelled = false;\n const loader = new window.Image();\n const handleLoaded = () => {\n if (!cancelled) setSource(previewSrc);\n };\n loader.addEventListener('load', handleLoaded);\n loader.src = previewSrc;\n return () => {\n cancelled = true;\n loader.removeEventListener('load', handleLoaded);\n };\n }, [currentEntry]);\n\n useEffect(\n () => () => {\n const thumbnailWasRemoved = !openerEntry.element.isConnected;\n if (thumbnailWasRemoved) endClosePreview(token);\n },\n [openerEntry, token],\n );\n\n return (\n <Dialog.Root modal open onOpenChange={handleDialogOpenChange}>\n <Dialog.Portal container={appElement ?? undefined}>\n <Dialog.Backdrop\n className={styles.viewerBackdrop}\n ref={backdropRef}\n style={zIndex === undefined ? undefined : { zIndex }}\n onClick={gestures.onSurfaceClick}\n />\n <Dialog.Popup\n aria-label={currentEntry.element.alt || undefined}\n className={styles.viewerPopup}\n finalFocus={finalFocus}\n // Focus the popup itself instead of Base UI's default (the first\n // tabbable — the close button): a focus-visible close button pins\n // the idle auto-hide open forever and pops its tooltip on open.\n initialFocus={popupNodeRef}\n ref={popupRef}\n style={zIndex === undefined ? undefined : { zIndex: zIndex + 1 }}\n tabIndex={-1}\n onClick={gestures.onSurfaceClick}\n onPointerCancel={gestures.onPointerFinish}\n onPointerDown={gestures.onPointerDown}\n onPointerMove={gestures.onPointerMove}\n onPointerUp={gestures.onPointerFinish}\n >\n <img\n alt={currentEntry.element.alt}\n className={styles.viewerImage}\n ref={imageRef}\n src={source}\n style={{\n cursor: gestures.cursor,\n height: imageRect.height,\n left: imageRect.x,\n top: imageRect.y,\n width: imageRect.width,\n }}\n onClick={gestures.onImageClick}\n onDoubleClick={gestures.onImageDoubleClick}\n onLoad={handleLoad}\n />\n <ViewerChrome\n canZoomIn={canZoomIn}\n canZoomOut={canZoomOut}\n chromeRef={chromeRef}\n current={currentIndex}\n fitRect={fitRect}\n flipHorizontal={flipHorizontal}\n flipVertical={flipVertical}\n hasNext={hasNext}\n hasPrev={hasPrev}\n natural={natural}\n next={next}\n prev={prev}\n reset={reset}\n rotateLeft={rotateLeft}\n rotateRight={rotateRight}\n rotation={rotation}\n scale={scale}\n source={source}\n toolbarAddon={currentEntry.options.toolbarAddon}\n total={entries.length}\n zoomIn={zoomIn}\n zoomOut={zoomOut}\n onClose={handleClose}\n />\n </Dialog.Popup>\n <ToastHost root={appElement ?? undefined} />\n </Dialog.Portal>\n </Dialog.Root>\n );\n});\n\nImageViewer.displayName = 'ImageViewer';\n\nexport default ImageViewer;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { usePreviewSession } from "./registry.mjs";
|
|
3
|
+
import ImageViewer from "./ImageViewer.mjs";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/Image/viewer/PreviewOutlet.tsx
|
|
6
|
+
const PreviewOutlet = ({ elementRef }) => {
|
|
7
|
+
const session = usePreviewSession(elementRef);
|
|
8
|
+
if (!session) return null;
|
|
9
|
+
return /* @__PURE__ */ jsx(ImageViewer, {
|
|
10
|
+
entries: session.entries,
|
|
11
|
+
index: session.index,
|
|
12
|
+
openerFocusElement: session.openerFocusElement,
|
|
13
|
+
token: session.token
|
|
14
|
+
}, session.token);
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { PreviewOutlet as default };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=PreviewOutlet.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PreviewOutlet.mjs","names":[],"sources":["../../../src/Image/viewer/PreviewOutlet.tsx"],"sourcesContent":["'use client';\n\nimport type { RefObject } from 'react';\n\nimport ImageViewer from './ImageViewer';\nimport { usePreviewSession } from './registry';\n\nexport interface PreviewOutletProps {\n elementRef: RefObject<HTMLImageElement | null>;\n}\n\nconst PreviewOutlet = ({ elementRef }: PreviewOutletProps) => {\n const session = usePreviewSession(elementRef);\n if (!session) return null;\n return (\n <ImageViewer\n entries={session.entries}\n index={session.index}\n key={session.token}\n openerFocusElement={session.openerFocusElement}\n token={session.token}\n />\n );\n};\n\nexport default PreviewOutlet;\n"],"mappings":";;;;;AAWA,MAAM,iBAAiB,EAAE,iBAAqC;CAC5D,MAAM,UAAU,kBAAkB,UAAU;CAC5C,IAAI,CAAC,SAAS,OAAO;CACrB,OACE,oBAAC,aAAD;EACE,SAAS,QAAQ;EACjB,OAAO,QAAQ;EAEf,oBAAoB,QAAQ;EAC5B,OAAO,QAAQ;CAChB,GAHM,QAAQ,KAGd;AAEL"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import FlexBasic_default from "../../Flex/FlexBasic.mjs";
|
|
3
|
+
import Center from "../../Flex/Center.mjs";
|
|
4
|
+
import { Tooltip } from "../../base-ui/Tooltip/Tooltip.mjs";
|
|
5
|
+
import TooltipGroup from "../../base-ui/Tooltip/TooltipGroup.mjs";
|
|
6
|
+
import ActionIcon from "../../ActionIcon/ActionIcon.mjs";
|
|
7
|
+
import DropdownMenu from "../../base-ui/DropdownMenu/DropdownMenu.mjs";
|
|
8
|
+
import { useTranslation } from "../../i18n/useTranslation.mjs";
|
|
9
|
+
import { downloadBlob } from "../../utils/downloadBlob.mjs";
|
|
10
|
+
import { styles } from "../style.mjs";
|
|
11
|
+
import { toast } from "../../base-ui/Toast/imperative.mjs";
|
|
12
|
+
import { naturalScale } from "./geometry.mjs";
|
|
13
|
+
import image_default from "../../i18n/resources/en/image.mjs";
|
|
14
|
+
import { getClipboardBlob } from "../../utils/blobToPng.mjs";
|
|
15
|
+
import { memo, useCallback, useMemo, useState, useSyncExternalStore } from "react";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
import { Copy, Download, Ellipsis, FlipHorizontal, FlipVertical, RotateCcw, RotateCw, ZoomIn, ZoomOut } from "lucide-react";
|
|
18
|
+
//#region src/Image/viewer/Toolbar.tsx
|
|
19
|
+
const getFileNameFromUrl = (url) => {
|
|
20
|
+
try {
|
|
21
|
+
const match = new URL(url).pathname.match(/\/([^/]+)$/);
|
|
22
|
+
return match ? decodeURIComponent(match[1]) : "image";
|
|
23
|
+
} catch {
|
|
24
|
+
return "image";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const getExtensionFromMimeType = (mimeType) => {
|
|
28
|
+
return {
|
|
29
|
+
"image/svg+xml": "svg",
|
|
30
|
+
"image/png": "png",
|
|
31
|
+
"image/jpeg": "jpg",
|
|
32
|
+
"image/jpg": "jpg",
|
|
33
|
+
"image/webp": "webp",
|
|
34
|
+
"image/gif": "gif"
|
|
35
|
+
}[mimeType?.toLowerCase()] || mimeType?.split("/")[1]?.split("+")[0] || "png";
|
|
36
|
+
};
|
|
37
|
+
const usePercentage = (scale, matchScale) => {
|
|
38
|
+
const subscribe = useCallback((onChange) => scale.on("change", onChange), [scale]);
|
|
39
|
+
const getSnapshot = useCallback(() => Math.round(scale.get() / matchScale * 100), [matchScale, scale]);
|
|
40
|
+
return useSyncExternalStore(subscribe, getSnapshot);
|
|
41
|
+
};
|
|
42
|
+
const Toolbar = memo(({ canZoomIn, canZoomOut, fitRect, flipHorizontal, flipVertical, natural, onMoreOpenChange, reset, rotateLeft, rotateRight, rotation, scale, source, toolbarAddon, zoomIn, zoomOut }) => {
|
|
43
|
+
const { t } = useTranslation(image_default);
|
|
44
|
+
const [containerEl, setContainerEl] = useState(null);
|
|
45
|
+
const [copyLoading, setCopyLoading] = useState(false);
|
|
46
|
+
const [downloadLoading, setDownloadLoading] = useState(false);
|
|
47
|
+
const matchScale = naturalScale(natural, fitRect, rotation);
|
|
48
|
+
const percentage = usePercentage(scale, matchScale);
|
|
49
|
+
const handleDownload = useCallback(async () => {
|
|
50
|
+
setDownloadLoading(true);
|
|
51
|
+
try {
|
|
52
|
+
const blob = await (await fetch(source, { mode: "cors" })).blob();
|
|
53
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
54
|
+
let fileName = getFileNameFromUrl(source);
|
|
55
|
+
const ext = getExtensionFromMimeType(blob.type);
|
|
56
|
+
if (!fileName.includes(".")) fileName = `${fileName}.${ext}`;
|
|
57
|
+
else if (fileName.endsWith(".svg+xml")) fileName = fileName.replace(/\.svg\+xml$/i, ".svg");
|
|
58
|
+
await downloadBlob(blobUrl, fileName);
|
|
59
|
+
URL.revokeObjectURL(blobUrl);
|
|
60
|
+
toast.success(t("image.downloadSuccess"));
|
|
61
|
+
} catch {
|
|
62
|
+
toast.error(t("image.downloadFailed"));
|
|
63
|
+
} finally {
|
|
64
|
+
setDownloadLoading(false);
|
|
65
|
+
}
|
|
66
|
+
}, [source, t]);
|
|
67
|
+
const handleCopy = useCallback(async () => {
|
|
68
|
+
setCopyLoading(true);
|
|
69
|
+
try {
|
|
70
|
+
const blob = await (await fetch(source, { mode: "cors" })).blob();
|
|
71
|
+
const clipboardBlob = await getClipboardBlob(blob);
|
|
72
|
+
await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);
|
|
73
|
+
toast.success(t("image.copySuccess"));
|
|
74
|
+
} catch {
|
|
75
|
+
toast.error(t("image.copyFailed"));
|
|
76
|
+
} finally {
|
|
77
|
+
setCopyLoading(false);
|
|
78
|
+
}
|
|
79
|
+
}, [source, t]);
|
|
80
|
+
const moreItems = useMemo(() => [
|
|
81
|
+
{
|
|
82
|
+
icon: FlipHorizontal,
|
|
83
|
+
key: "flip-horizontal",
|
|
84
|
+
label: t("image.flipHorizontal"),
|
|
85
|
+
onClick: flipHorizontal
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
icon: FlipVertical,
|
|
89
|
+
key: "flip-vertical",
|
|
90
|
+
label: t("image.flipVertical"),
|
|
91
|
+
onClick: flipVertical
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
icon: RotateCcw,
|
|
95
|
+
key: "rotate-left",
|
|
96
|
+
label: t("image.rotateLeft"),
|
|
97
|
+
onClick: rotateLeft
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
icon: RotateCw,
|
|
101
|
+
key: "rotate-right",
|
|
102
|
+
label: t("image.rotateRight"),
|
|
103
|
+
onClick: rotateRight
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
icon: Copy,
|
|
107
|
+
key: "copy",
|
|
108
|
+
label: t("image.copy"),
|
|
109
|
+
onClick: handleCopy
|
|
110
|
+
}
|
|
111
|
+
], [
|
|
112
|
+
flipHorizontal,
|
|
113
|
+
flipVertical,
|
|
114
|
+
handleCopy,
|
|
115
|
+
rotateLeft,
|
|
116
|
+
rotateRight,
|
|
117
|
+
t
|
|
118
|
+
]);
|
|
119
|
+
return /* @__PURE__ */ jsx(TooltipGroup, {
|
|
120
|
+
popupContainer: containerEl ?? void 0,
|
|
121
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
122
|
+
className: styles.toolbar,
|
|
123
|
+
ref: setContainerEl,
|
|
124
|
+
children: /* @__PURE__ */ jsxs(FlexBasic_default, {
|
|
125
|
+
horizontal: true,
|
|
126
|
+
align: "center",
|
|
127
|
+
className: styles.toolbarRow,
|
|
128
|
+
gap: 8,
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ jsx(ActionIcon, {
|
|
131
|
+
disabled: !canZoomOut,
|
|
132
|
+
icon: ZoomOut,
|
|
133
|
+
style: { borderRadius: 999 },
|
|
134
|
+
title: t("image.zoomOut"),
|
|
135
|
+
onClick: zoomOut
|
|
136
|
+
}),
|
|
137
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
138
|
+
title: t("image.zoomReset"),
|
|
139
|
+
children: /* @__PURE__ */ jsxs(Center, {
|
|
140
|
+
horizontal: true,
|
|
141
|
+
className: styles.toolbarPercentage,
|
|
142
|
+
role: "button",
|
|
143
|
+
tabIndex: 0,
|
|
144
|
+
onClick: reset,
|
|
145
|
+
children: [percentage, "%"]
|
|
146
|
+
})
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx(ActionIcon, {
|
|
149
|
+
disabled: !canZoomIn,
|
|
150
|
+
icon: ZoomIn,
|
|
151
|
+
style: { borderRadius: 999 },
|
|
152
|
+
title: t("image.zoomIn"),
|
|
153
|
+
onClick: zoomIn
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ jsx(ActionIcon, {
|
|
156
|
+
icon: Download,
|
|
157
|
+
loading: downloadLoading,
|
|
158
|
+
style: { borderRadius: 999 },
|
|
159
|
+
title: t("image.download"),
|
|
160
|
+
onClick: handleDownload
|
|
161
|
+
}),
|
|
162
|
+
/* @__PURE__ */ jsx(DropdownMenu, {
|
|
163
|
+
items: moreItems,
|
|
164
|
+
placement: "top",
|
|
165
|
+
portalProps: { container: containerEl ?? void 0 },
|
|
166
|
+
onOpenChange: (open) => onMoreOpenChange?.(open),
|
|
167
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
168
|
+
icon: Ellipsis,
|
|
169
|
+
loading: copyLoading,
|
|
170
|
+
style: { borderRadius: 999 },
|
|
171
|
+
title: t("image.more")
|
|
172
|
+
})
|
|
173
|
+
}),
|
|
174
|
+
toolbarAddon
|
|
175
|
+
]
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
Toolbar.displayName = "Toolbar";
|
|
181
|
+
//#endregion
|
|
182
|
+
export { Toolbar as default };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=Toolbar.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toolbar.mjs","names":["imageMessages","Flexbox"],"sources":["../../../src/Image/viewer/Toolbar.tsx"],"sourcesContent":["'use client';\n\nimport {\n Copy,\n Download,\n Ellipsis,\n FlipHorizontal,\n FlipVertical,\n RotateCcw,\n RotateCw,\n ZoomIn,\n ZoomOut,\n} from 'lucide-react';\nimport type { MotionValue } from 'motion/react';\nimport { memo, type ReactNode, useCallback, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport ActionIcon from '@/ActionIcon';\nimport DropdownMenu, { type DropdownItem } from '@/base-ui/DropdownMenu';\nimport { toast } from '@/base-ui/Toast';\nimport { Center, Flexbox } from '@/Flex';\nimport imageMessages from '@/i18n/resources/en/image';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport Tooltip, { TooltipGroup } from '@/Tooltip';\nimport { getClipboardBlob } from '@/utils/blobToPng';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { styles } from '../style';\nimport { naturalScale, type Rect, type Rotation, type Size } from './geometry';\n\nconst getFileNameFromUrl = (url: string): string => {\n try {\n const pathname = new URL(url).pathname;\n const match = pathname.match(/\\/([^/]+)$/);\n return match ? decodeURIComponent(match[1]) : 'image';\n } catch {\n return 'image';\n }\n};\n\nconst getExtensionFromMimeType = (mimeType: string): string => {\n const map: Record<string, string> = {\n 'image/svg+xml': 'svg',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n };\n return map[mimeType?.toLowerCase()] || mimeType?.split('/')[1]?.split('+')[0] || 'png';\n};\n\nconst usePercentage = (scale: MotionValue<number>, matchScale: number): number => {\n const subscribe = useCallback((onChange: () => void) => scale.on('change', onChange), [scale]);\n const getSnapshot = useCallback(\n () => Math.round((scale.get() / matchScale) * 100),\n [matchScale, scale],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n\nexport interface ToolbarProps {\n canZoomIn: boolean;\n canZoomOut: boolean;\n fitRect: Rect;\n flipHorizontal: () => void;\n flipVertical: () => void;\n natural: Size;\n onMoreOpenChange?: (open: boolean) => void;\n reset: () => void;\n rotateLeft: () => void;\n rotateRight: () => void;\n rotation: Rotation;\n scale: MotionValue<number>;\n source: string;\n toolbarAddon?: ReactNode;\n zoomIn: () => void;\n zoomOut: () => void;\n}\n\nconst Toolbar = memo<ToolbarProps>(\n ({\n canZoomIn,\n canZoomOut,\n fitRect,\n flipHorizontal,\n flipVertical,\n natural,\n onMoreOpenChange,\n reset,\n rotateLeft,\n rotateRight,\n rotation,\n scale,\n source,\n toolbarAddon,\n zoomIn,\n zoomOut,\n }) => {\n const { t } = useTranslation(imageMessages);\n const [containerEl, setContainerEl] = useState<HTMLElement | null>(null);\n const [copyLoading, setCopyLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n\n const matchScale = naturalScale(natural, fitRect, rotation);\n const percentage = usePercentage(scale, matchScale);\n\n const handleDownload = useCallback(async () => {\n setDownloadLoading(true);\n try {\n const response = await fetch(source, { mode: 'cors' });\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n let fileName = getFileNameFromUrl(source);\n const ext = getExtensionFromMimeType(blob.type);\n if (!fileName.includes('.')) {\n fileName = `${fileName}.${ext}`;\n } else if (fileName.endsWith('.svg+xml')) {\n fileName = fileName.replace(/\\.svg\\+xml$/i, '.svg');\n }\n await downloadBlob(blobUrl, fileName);\n URL.revokeObjectURL(blobUrl);\n toast.success(t('image.downloadSuccess'));\n } catch {\n toast.error(t('image.downloadFailed'));\n } finally {\n setDownloadLoading(false);\n }\n }, [source, t]);\n\n const handleCopy = useCallback(async () => {\n setCopyLoading(true);\n try {\n const response = await fetch(source, { mode: 'cors' });\n const blob = await response.blob();\n const clipboardBlob = await getClipboardBlob(blob);\n await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);\n toast.success(t('image.copySuccess'));\n } catch {\n toast.error(t('image.copyFailed'));\n } finally {\n setCopyLoading(false);\n }\n }, [source, t]);\n\n const moreItems = useMemo<DropdownItem[]>(\n () => [\n {\n icon: FlipHorizontal,\n key: 'flip-horizontal',\n label: t('image.flipHorizontal'),\n onClick: flipHorizontal,\n },\n {\n icon: FlipVertical,\n key: 'flip-vertical',\n label: t('image.flipVertical'),\n onClick: flipVertical,\n },\n { icon: RotateCcw, key: 'rotate-left', label: t('image.rotateLeft'), onClick: rotateLeft },\n {\n icon: RotateCw,\n key: 'rotate-right',\n label: t('image.rotateRight'),\n onClick: rotateRight,\n },\n { icon: Copy, key: 'copy', label: t('image.copy'), onClick: handleCopy },\n ],\n [flipHorizontal, flipVertical, handleCopy, rotateLeft, rotateRight, t],\n );\n\n return (\n <TooltipGroup popupContainer={containerEl ?? undefined}>\n <div className={styles.toolbar} ref={setContainerEl}>\n <Flexbox horizontal align=\"center\" className={styles.toolbarRow} gap={8}>\n <ActionIcon\n disabled={!canZoomOut}\n icon={ZoomOut}\n style={{ borderRadius: 999 }}\n title={t('image.zoomOut')}\n onClick={zoomOut}\n />\n <Tooltip title={t('image.zoomReset')}>\n <Center\n horizontal\n className={styles.toolbarPercentage}\n role=\"button\"\n tabIndex={0}\n onClick={reset}\n >\n {percentage}%\n </Center>\n </Tooltip>\n <ActionIcon\n disabled={!canZoomIn}\n icon={ZoomIn}\n style={{ borderRadius: 999 }}\n title={t('image.zoomIn')}\n onClick={zoomIn}\n />\n <ActionIcon\n icon={Download}\n loading={downloadLoading}\n style={{ borderRadius: 999 }}\n title={t('image.download')}\n onClick={handleDownload}\n />\n <DropdownMenu\n items={moreItems}\n placement=\"top\"\n portalProps={{ container: containerEl ?? undefined }}\n onOpenChange={(open) => onMoreOpenChange?.(open)}\n >\n <ActionIcon\n icon={Ellipsis}\n loading={copyLoading}\n style={{ borderRadius: 999 }}\n title={t('image.more')}\n />\n </DropdownMenu>\n {toolbarAddon}\n </Flexbox>\n </div>\n </TooltipGroup>\n );\n },\n);\n\nToolbar.displayName = 'Toolbar';\n\nexport default Toolbar;\n"],"mappings":"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"}
|