@djangocfg/ui-tools 2.1.110 → 2.1.112

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 (159) hide show
  1. package/README.md +242 -49
  2. package/dist/JsonSchemaForm-65NLLK56.mjs +4 -0
  3. package/dist/JsonSchemaForm-65NLLK56.mjs.map +1 -0
  4. package/dist/JsonSchemaForm-PY6DH3HE.cjs +13 -0
  5. package/dist/JsonSchemaForm-PY6DH3HE.cjs.map +1 -0
  6. package/dist/JsonTree-6RYAOPSS.mjs +4 -0
  7. package/dist/JsonTree-6RYAOPSS.mjs.map +1 -0
  8. package/dist/JsonTree-7OH6CIHT.cjs +10 -0
  9. package/dist/JsonTree-7OH6CIHT.cjs.map +1 -0
  10. package/dist/MapContainer-GXQLP5WY.mjs +214 -0
  11. package/dist/MapContainer-GXQLP5WY.mjs.map +1 -0
  12. package/dist/MapContainer-RYG4HPH4.cjs +221 -0
  13. package/dist/MapContainer-RYG4HPH4.cjs.map +1 -0
  14. package/dist/{Mermaid.client-4OCKJ6QD.mjs → Mermaid.client-OKACITCW.mjs} +16 -7
  15. package/dist/Mermaid.client-OKACITCW.mjs.map +1 -0
  16. package/dist/{Mermaid.client-ZP6OE46Z.cjs → Mermaid.client-PNXEC6YL.cjs} +16 -7
  17. package/dist/Mermaid.client-PNXEC6YL.cjs.map +1 -0
  18. package/dist/{PlaygroundLayout-XXVBU4WZ.cjs → PlaygroundLayout-SYMEAG3J.cjs} +25 -24
  19. package/dist/PlaygroundLayout-SYMEAG3J.cjs.map +1 -0
  20. package/dist/{PlaygroundLayout-LMQTVXSP.mjs → PlaygroundLayout-UQRBU5RH.mjs} +4 -3
  21. package/dist/PlaygroundLayout-UQRBU5RH.mjs.map +1 -0
  22. package/dist/{PrettyCode.client-2CLSV2VD.cjs → PrettyCode.client-DANYYQYO.cjs} +11 -4
  23. package/dist/PrettyCode.client-DANYYQYO.cjs.map +1 -0
  24. package/dist/{PrettyCode.client-Y2BVON7R.mjs → PrettyCode.client-RS5ZTNBT.mjs} +11 -4
  25. package/dist/PrettyCode.client-RS5ZTNBT.mjs.map +1 -0
  26. package/dist/chunk-2DSR7V2L.mjs +561 -0
  27. package/dist/chunk-2DSR7V2L.mjs.map +1 -0
  28. package/dist/chunk-47T5ECYV.cjs +1357 -0
  29. package/dist/chunk-47T5ECYV.cjs.map +1 -0
  30. package/dist/chunk-5QT3QYFZ.cjs +189 -0
  31. package/dist/chunk-5QT3QYFZ.cjs.map +1 -0
  32. package/dist/chunk-7IIRYG4S.mjs +1057 -0
  33. package/dist/chunk-7IIRYG4S.mjs.map +1 -0
  34. package/dist/{chunk-FB5QBSI3.cjs → chunk-DI3HUXHK.cjs} +15 -195
  35. package/dist/chunk-DI3HUXHK.cjs.map +1 -0
  36. package/dist/chunk-EVGWYASL.cjs +1528 -0
  37. package/dist/chunk-EVGWYASL.cjs.map +1 -0
  38. package/dist/chunk-F2N7P5XU.cjs +30 -0
  39. package/dist/chunk-F2N7P5XU.cjs.map +1 -0
  40. package/dist/{chunk-L6UHASYQ.mjs → chunk-G6PRZP5I.mjs} +7 -186
  41. package/dist/chunk-G6PRZP5I.mjs.map +1 -0
  42. package/dist/chunk-JWB2EWQO.mjs +5 -0
  43. package/dist/chunk-JWB2EWQO.mjs.map +1 -0
  44. package/dist/chunk-LTJX2JXE.mjs +338 -0
  45. package/dist/chunk-LTJX2JXE.mjs.map +1 -0
  46. package/dist/chunk-OVNC4KW6.mjs +1494 -0
  47. package/dist/chunk-OVNC4KW6.mjs.map +1 -0
  48. package/dist/chunk-PNZSJN6T.cjs +1086 -0
  49. package/dist/chunk-PNZSJN6T.cjs.map +1 -0
  50. package/dist/chunk-TEFRA7GW.cjs +565 -0
  51. package/dist/chunk-TEFRA7GW.cjs.map +1 -0
  52. package/dist/chunk-UOMPPIED.mjs +1343 -0
  53. package/dist/chunk-UOMPPIED.mjs.map +1 -0
  54. package/dist/chunk-W6YHQI4F.mjs +187 -0
  55. package/dist/chunk-W6YHQI4F.mjs.map +1 -0
  56. package/dist/chunk-XTBRWVIV.cjs +346 -0
  57. package/dist/chunk-XTBRWVIV.cjs.map +1 -0
  58. package/dist/components-C7ZL7OMY.mjs +5 -0
  59. package/dist/components-C7ZL7OMY.mjs.map +1 -0
  60. package/dist/components-CJ2IB65O.cjs +27 -0
  61. package/dist/components-CJ2IB65O.cjs.map +1 -0
  62. package/dist/components-EASJYK45.mjs +6 -0
  63. package/dist/components-EASJYK45.mjs.map +1 -0
  64. package/dist/components-LDRFDV4A.cjs +22 -0
  65. package/dist/components-LDRFDV4A.cjs.map +1 -0
  66. package/dist/components-VZKUTDJK.mjs +5 -0
  67. package/dist/components-VZKUTDJK.mjs.map +1 -0
  68. package/dist/components-Y64GTIMQ.cjs +42 -0
  69. package/dist/components-Y64GTIMQ.cjs.map +1 -0
  70. package/dist/index.cjs +701 -4813
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/index.d.cts +1274 -1026
  73. package/dist/index.d.ts +1274 -1026
  74. package/dist/index.mjs +358 -4730
  75. package/dist/index.mjs.map +1 -1
  76. package/package.json +27 -4
  77. package/src/components/index.ts +17 -0
  78. package/src/components/lazy-wrapper.tsx +281 -0
  79. package/src/index.ts +92 -7
  80. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +14 -5
  81. package/src/tools/AudioPlayer/lazy.tsx +85 -0
  82. package/src/tools/Gallery/components/Gallery.tsx +182 -0
  83. package/src/tools/Gallery/components/GalleryCarousel.tsx +251 -0
  84. package/src/tools/Gallery/components/GalleryCompact.tsx +173 -0
  85. package/src/tools/Gallery/components/GalleryGrid.tsx +493 -0
  86. package/src/tools/Gallery/components/GalleryImage.tsx +66 -0
  87. package/src/tools/Gallery/components/GalleryLightbox.tsx +331 -0
  88. package/src/tools/Gallery/components/GalleryMedia.tsx +66 -0
  89. package/src/tools/Gallery/components/GalleryThumbnails.tsx +173 -0
  90. package/src/tools/Gallery/components/GalleryThumbnailsVirtual.tsx +138 -0
  91. package/src/tools/Gallery/components/GalleryVideo.tsx +222 -0
  92. package/src/tools/Gallery/components/index.ts +13 -0
  93. package/src/tools/Gallery/hooks/index.ts +23 -0
  94. package/src/tools/Gallery/hooks/useGallery.ts +137 -0
  95. package/src/tools/Gallery/hooks/useImageDimensions.ts +223 -0
  96. package/src/tools/Gallery/hooks/usePinchZoom.ts +234 -0
  97. package/src/tools/Gallery/hooks/usePreloadImages.ts +71 -0
  98. package/src/tools/Gallery/hooks/useSwipe.ts +86 -0
  99. package/src/tools/Gallery/hooks/useVirtualList.ts +129 -0
  100. package/src/tools/Gallery/hooks/useZoom.ts +316 -0
  101. package/src/tools/Gallery/index.ts +66 -0
  102. package/src/tools/Gallery/types.ts +183 -0
  103. package/src/tools/Gallery/utils/imageAnalysis.ts +52 -0
  104. package/src/tools/Gallery/utils/index.ts +11 -0
  105. package/src/tools/ImageViewer/components/ImageToolbar.tsx +20 -8
  106. package/src/tools/ImageViewer/components/ImageViewer.tsx +12 -4
  107. package/src/tools/ImageViewer/lazy.tsx +37 -0
  108. package/src/tools/JsonForm/lazy.tsx +43 -0
  109. package/src/tools/JsonForm/widgets/ColorWidget.tsx +4 -1
  110. package/src/tools/JsonTree/lazy.tsx +45 -0
  111. package/src/tools/LottiePlayer/lazy.tsx +57 -0
  112. package/src/tools/Map/components/CustomOverlay.tsx +54 -0
  113. package/src/tools/Map/components/DrawControl.tsx +36 -0
  114. package/src/tools/Map/components/GeocoderControl.tsx +70 -0
  115. package/src/tools/Map/components/LayerSwitcher.tsx +225 -0
  116. package/src/tools/Map/components/MapCluster.tsx +273 -0
  117. package/src/tools/Map/components/MapContainer.tsx +191 -0
  118. package/src/tools/Map/components/MapControls.tsx +44 -0
  119. package/src/tools/Map/components/MapLegend.tsx +161 -0
  120. package/src/tools/Map/components/MapMarker.tsx +102 -0
  121. package/src/tools/Map/components/MapPopup.tsx +46 -0
  122. package/src/tools/Map/components/MapSource.tsx +30 -0
  123. package/src/tools/Map/components/index.ts +20 -0
  124. package/src/tools/Map/context/MapContext.tsx +89 -0
  125. package/src/tools/Map/context/index.ts +2 -0
  126. package/src/tools/Map/hooks/index.ts +9 -0
  127. package/src/tools/Map/hooks/useMap.ts +11 -0
  128. package/src/tools/Map/hooks/useMapControl.ts +99 -0
  129. package/src/tools/Map/hooks/useMapEvents.ts +147 -0
  130. package/src/tools/Map/hooks/useMapLayers.ts +83 -0
  131. package/src/tools/Map/hooks/useMapViewport.ts +62 -0
  132. package/src/tools/Map/hooks/useMarkers.ts +85 -0
  133. package/src/tools/Map/index.ts +116 -0
  134. package/src/tools/Map/layers/cluster.ts +94 -0
  135. package/src/tools/Map/layers/index.ts +15 -0
  136. package/src/tools/Map/layers/line.ts +93 -0
  137. package/src/tools/Map/layers/point.ts +61 -0
  138. package/src/tools/Map/layers/polygon.ts +73 -0
  139. package/src/tools/Map/lazy.tsx +56 -0
  140. package/src/tools/Map/styles/index.ts +15 -0
  141. package/src/tools/Map/types.ts +259 -0
  142. package/src/tools/Map/utils/geo.ts +88 -0
  143. package/src/tools/Map/utils/index.ts +16 -0
  144. package/src/tools/Map/utils/transform.ts +107 -0
  145. package/src/tools/Mermaid/Mermaid.client.tsx +12 -4
  146. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +6 -2
  147. package/src/tools/Mermaid/lazy.tsx +46 -0
  148. package/src/tools/OpenapiViewer/lazy.tsx +72 -0
  149. package/src/tools/PrettyCode/PrettyCode.client.tsx +10 -3
  150. package/src/tools/PrettyCode/lazy.tsx +64 -0
  151. package/src/tools/VideoPlayer/lazy.tsx +63 -0
  152. package/dist/Mermaid.client-4OCKJ6QD.mjs.map +0 -1
  153. package/dist/Mermaid.client-ZP6OE46Z.cjs.map +0 -1
  154. package/dist/PlaygroundLayout-LMQTVXSP.mjs.map +0 -1
  155. package/dist/PlaygroundLayout-XXVBU4WZ.cjs.map +0 -1
  156. package/dist/PrettyCode.client-2CLSV2VD.cjs.map +0 -1
  157. package/dist/PrettyCode.client-Y2BVON7R.mjs.map +0 -1
  158. package/dist/chunk-FB5QBSI3.cjs.map +0 -1
  159. package/dist/chunk-L6UHASYQ.mjs.map +0 -1
@@ -0,0 +1,565 @@
1
+ 'use strict';
2
+
3
+ var chunkXTBRWVIV_cjs = require('./chunk-XTBRWVIV.cjs');
4
+ var chunkWGEGR3DF_cjs = require('./chunk-WGEGR3DF.cjs');
5
+ var react = require('react');
6
+ var lucideReact = require('lucide-react');
7
+ var reactZoomPanPinch = require('react-zoom-pan-pinch');
8
+ var uiCore = require('@djangocfg/ui-core');
9
+ var i18n = require('@djangocfg/i18n');
10
+ var components = require('@djangocfg/ui-core/components');
11
+ var lib = require('@djangocfg/ui-core/lib');
12
+ var jsxRuntime = require('react/jsx-runtime');
13
+
14
+ // src/tools/ImageViewer/utils/constants.ts
15
+ var MAX_IMAGE_SIZE = 50 * 1024 * 1024;
16
+ var WARNING_IMAGE_SIZE = 10 * 1024 * 1024;
17
+ var PROGRESSIVE_LOADING_THRESHOLD = 500 * 1024;
18
+ var LQIP_SIZE = 32;
19
+ var LQIP_QUALITY = 0.5;
20
+ var ZOOM_PRESETS = [
21
+ { label: "Fit", value: "fit" },
22
+ { label: "25%", value: 0.25 },
23
+ { label: "50%", value: 0.5 },
24
+ { label: "100%", value: 1 },
25
+ { label: "200%", value: 2 },
26
+ { label: "400%", value: 4 }
27
+ ];
28
+ var DEFAULT_TRANSFORM = {
29
+ rotation: 0,
30
+ flipH: false,
31
+ flipV: false
32
+ };
33
+
34
+ // src/tools/ImageViewer/utils/lqip.ts
35
+ async function createLQIP(imageSrc) {
36
+ try {
37
+ const img = new Image();
38
+ img.crossOrigin = "anonymous";
39
+ await new Promise((resolve, reject) => {
40
+ img.onload = () => resolve();
41
+ img.onerror = () => reject(new Error("Failed to load image for LQIP"));
42
+ img.src = imageSrc;
43
+ });
44
+ const aspect = img.naturalWidth / img.naturalHeight;
45
+ const width = aspect >= 1 ? LQIP_SIZE : Math.round(LQIP_SIZE * aspect);
46
+ const height = aspect >= 1 ? Math.round(LQIP_SIZE / aspect) : LQIP_SIZE;
47
+ const canvas = document.createElement("canvas");
48
+ canvas.width = width;
49
+ canvas.height = height;
50
+ const ctx = canvas.getContext("2d");
51
+ if (!ctx) return null;
52
+ ctx.drawImage(img, 0, 0, width, height);
53
+ return canvas.toDataURL("image/jpeg", LQIP_QUALITY);
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ chunkWGEGR3DF_cjs.__name(createLQIP, "createLQIP");
59
+ var imageDebug = lib.createMediaLogger("ImageViewer");
60
+ function ImageToolbar({
61
+ scale,
62
+ transform,
63
+ onRotate,
64
+ onFlipH,
65
+ onFlipV,
66
+ onZoomPreset,
67
+ onExpand
68
+ }) {
69
+ const t = i18n.useTypedT();
70
+ const { zoomIn, zoomOut, resetTransform } = reactZoomPanPinch.useControls();
71
+ const labels = react.useMemo(() => ({
72
+ zoomIn: t("tools.image.zoomIn"),
73
+ zoomOut: t("tools.image.zoomOut"),
74
+ fitToView: t("tools.image.fitToView"),
75
+ flipHorizontal: t("tools.image.flipHorizontal"),
76
+ flipVertical: t("tools.image.flipVertical"),
77
+ rotate: t("tools.image.rotate"),
78
+ fullscreen: t("tools.image.fullscreen")
79
+ }), [t]);
80
+ const zoomLabel = react.useMemo(() => {
81
+ const percent = Math.round(scale * 100);
82
+ return `${percent}%`;
83
+ }, [scale]);
84
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-0.5 bg-background/90 backdrop-blur-sm border rounded-lg p-1 shadow-lg", children: [
85
+ /* @__PURE__ */ jsxRuntime.jsx(
86
+ components.Button,
87
+ {
88
+ variant: "ghost",
89
+ size: "icon",
90
+ className: "h-7 w-7",
91
+ onClick: () => zoomOut(),
92
+ title: labels.zoomOut,
93
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomOut, { className: "h-3.5 w-3.5" })
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsxRuntime.jsxs(components.DropdownMenu, { children: [
97
+ /* @__PURE__ */ jsxRuntime.jsx(components.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant: "ghost", size: "sm", className: "h-7 px-2 min-w-[52px] font-mono text-xs", children: zoomLabel }) }),
98
+ /* @__PURE__ */ jsxRuntime.jsx(components.DropdownMenuContent, { align: "center", className: "min-w-[80px]", children: ZOOM_PRESETS.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
99
+ components.DropdownMenuItem,
100
+ {
101
+ onClick: () => onZoomPreset(preset.value),
102
+ className: "text-xs justify-center",
103
+ children: preset.label
104
+ },
105
+ preset.label
106
+ )) })
107
+ ] }),
108
+ /* @__PURE__ */ jsxRuntime.jsx(
109
+ components.Button,
110
+ {
111
+ variant: "ghost",
112
+ size: "icon",
113
+ className: "h-7 w-7",
114
+ onClick: () => zoomIn(),
115
+ title: labels.zoomIn,
116
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomIn, { className: "h-3.5 w-3.5" })
117
+ }
118
+ ),
119
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-border mx-1" }),
120
+ /* @__PURE__ */ jsxRuntime.jsx(
121
+ components.Button,
122
+ {
123
+ variant: "ghost",
124
+ size: "icon",
125
+ className: "h-7 w-7",
126
+ onClick: () => resetTransform(),
127
+ title: labels.fitToView,
128
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-3.5 w-3.5" })
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-border mx-1" }),
132
+ /* @__PURE__ */ jsxRuntime.jsx(
133
+ components.Button,
134
+ {
135
+ variant: "ghost",
136
+ size: "icon",
137
+ className: lib.cn("h-7 w-7", transform.flipH && "bg-accent"),
138
+ onClick: onFlipH,
139
+ title: labels.flipHorizontal,
140
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FlipHorizontal, { className: "h-3.5 w-3.5" })
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsxRuntime.jsx(
144
+ components.Button,
145
+ {
146
+ variant: "ghost",
147
+ size: "icon",
148
+ className: lib.cn("h-7 w-7", transform.flipV && "bg-accent"),
149
+ onClick: onFlipV,
150
+ title: labels.flipVertical,
151
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FlipVertical, { className: "h-3.5 w-3.5" })
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsxRuntime.jsx(
155
+ components.Button,
156
+ {
157
+ variant: "ghost",
158
+ size: "icon",
159
+ className: "h-7 w-7",
160
+ onClick: onRotate,
161
+ title: labels.rotate,
162
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCw, { className: "h-3.5 w-3.5" })
163
+ }
164
+ ),
165
+ transform.rotation !== 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground font-mono pl-1", children: [
166
+ transform.rotation,
167
+ "\xB0"
168
+ ] }),
169
+ onExpand && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
170
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-border mx-1" }),
171
+ /* @__PURE__ */ jsxRuntime.jsx(
172
+ components.Button,
173
+ {
174
+ variant: "ghost",
175
+ size: "icon",
176
+ className: "h-7 w-7",
177
+ onClick: onExpand,
178
+ title: labels.fullscreen,
179
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Expand, { className: "h-3.5 w-3.5" })
180
+ }
181
+ )
182
+ ] })
183
+ ] });
184
+ }
185
+ chunkWGEGR3DF_cjs.__name(ImageToolbar, "ImageToolbar");
186
+ function ImageInfo({ src }) {
187
+ const { getDimensions, cacheDimensions } = chunkXTBRWVIV_cjs.useImageCache();
188
+ const [dimensions, setDimensions] = react.useState(null);
189
+ react.useEffect(() => {
190
+ const cached = getDimensions(src);
191
+ if (cached) {
192
+ setDimensions({ w: cached.width, h: cached.height });
193
+ return;
194
+ }
195
+ const img = new Image();
196
+ img.onload = () => {
197
+ const dims = { w: img.naturalWidth, h: img.naturalHeight };
198
+ setDimensions(dims);
199
+ cacheDimensions(src, { width: dims.w, height: dims.h });
200
+ };
201
+ img.src = src;
202
+ }, [src, getDimensions, cacheDimensions]);
203
+ if (!dimensions) return null;
204
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-3 right-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono", children: [
205
+ dimensions.w,
206
+ " \xD7 ",
207
+ dimensions.h
208
+ ] });
209
+ }
210
+ chunkWGEGR3DF_cjs.__name(ImageInfo, "ImageInfo");
211
+ function useImageTransform(options = {}) {
212
+ const { resetKey } = options;
213
+ const [transform, setTransform] = react.useState(DEFAULT_TRANSFORM);
214
+ react.useEffect(() => {
215
+ setTransform(DEFAULT_TRANSFORM);
216
+ }, [resetKey]);
217
+ const rotate = react.useCallback(() => {
218
+ setTransform((prev) => ({
219
+ ...prev,
220
+ rotation: (prev.rotation + 90) % 360
221
+ }));
222
+ }, []);
223
+ const flipH = react.useCallback(() => {
224
+ setTransform((prev) => ({
225
+ ...prev,
226
+ flipH: !prev.flipH
227
+ }));
228
+ }, []);
229
+ const flipV = react.useCallback(() => {
230
+ setTransform((prev) => ({
231
+ ...prev,
232
+ flipV: !prev.flipV
233
+ }));
234
+ }, []);
235
+ const reset = react.useCallback(() => {
236
+ setTransform(DEFAULT_TRANSFORM);
237
+ }, []);
238
+ const transformStyle = react.useMemo(() => {
239
+ const transforms = [];
240
+ if (transform.rotation !== 0) {
241
+ transforms.push(`rotate(${transform.rotation}deg)`);
242
+ }
243
+ if (transform.flipH) {
244
+ transforms.push("scaleX(-1)");
245
+ }
246
+ if (transform.flipV) {
247
+ transforms.push("scaleY(-1)");
248
+ }
249
+ return transforms.join(" ") || "none";
250
+ }, [transform]);
251
+ return {
252
+ transform,
253
+ rotate,
254
+ flipH,
255
+ flipV,
256
+ reset,
257
+ transformStyle
258
+ };
259
+ }
260
+ chunkWGEGR3DF_cjs.__name(useImageTransform, "useImageTransform");
261
+ function useImageLoading(options) {
262
+ const { content, mimeType, src: directSrc } = options;
263
+ const getOrCreateBlobUrl = chunkXTBRWVIV_cjs.useMediaCacheStore.getState().getOrCreateBlobUrl;
264
+ const releaseBlobUrl = chunkXTBRWVIV_cjs.useMediaCacheStore.getState().releaseBlobUrl;
265
+ const [src, setSrc] = react.useState(null);
266
+ const [lqip, setLqip] = react.useState(null);
267
+ const [isFullyLoaded, setIsFullyLoaded] = react.useState(false);
268
+ const [error, setError] = react.useState(null);
269
+ const contentKeyRef = react.useRef(null);
270
+ const isMountedRef = react.useRef(true);
271
+ const size = content ? typeof content === "string" ? content.length : content.byteLength : 0;
272
+ const hasContent = directSrc ? true : size > 0;
273
+ const useProgressiveLoading = directSrc ? false : size > PROGRESSIVE_LOADING_THRESHOLD;
274
+ react.useEffect(() => {
275
+ isMountedRef.current = true;
276
+ return () => {
277
+ isMountedRef.current = false;
278
+ if (contentKeyRef.current) {
279
+ chunkXTBRWVIV_cjs.useMediaCacheStore.getState().releaseBlobUrl(contentKeyRef.current);
280
+ contentKeyRef.current = null;
281
+ }
282
+ };
283
+ }, []);
284
+ react.useEffect(() => {
285
+ setError(null);
286
+ if (directSrc) {
287
+ imageDebug.load(directSrc, "url");
288
+ setSrc(directSrc);
289
+ setIsFullyLoaded(true);
290
+ return;
291
+ }
292
+ if (!hasContent) {
293
+ setSrc(null);
294
+ return;
295
+ }
296
+ if (size > MAX_IMAGE_SIZE) {
297
+ const sizeMB = (size / 1024 / 1024).toFixed(1);
298
+ const errorMsg = `Image too large: ${sizeMB}MB (maximum: 50MB)`;
299
+ imageDebug.error(errorMsg, { size, sizeMB, maxSize: MAX_IMAGE_SIZE });
300
+ setError(errorMsg);
301
+ setSrc(null);
302
+ return;
303
+ }
304
+ if (size > WARNING_IMAGE_SIZE) {
305
+ const sizeMB = (size / 1024 / 1024).toFixed(1);
306
+ imageDebug.warn(`Large image: ${sizeMB}MB - may impact performance`);
307
+ }
308
+ if (typeof content === "string") {
309
+ if (content.startsWith("data:")) {
310
+ imageDebug.load(content.slice(0, 50) + "...", "data-url");
311
+ setSrc(content);
312
+ return;
313
+ }
314
+ const encoder = new TextEncoder();
315
+ const buffer = encoder.encode(content).buffer;
316
+ const contentKey2 = chunkXTBRWVIV_cjs.generateContentKey(buffer);
317
+ if (contentKeyRef.current && contentKeyRef.current !== contentKey2) {
318
+ releaseBlobUrl(contentKeyRef.current);
319
+ }
320
+ contentKeyRef.current = contentKey2;
321
+ const url2 = getOrCreateBlobUrl(contentKey2, buffer, mimeType || "image/png");
322
+ imageDebug.load(url2, "blob");
323
+ imageDebug.state("loaded", { size, mimeType, contentKey: contentKey2 });
324
+ setSrc(url2);
325
+ return;
326
+ }
327
+ const contentKey = chunkXTBRWVIV_cjs.generateContentKey(content);
328
+ if (contentKeyRef.current && contentKeyRef.current !== contentKey) {
329
+ releaseBlobUrl(contentKeyRef.current);
330
+ }
331
+ contentKeyRef.current = contentKey;
332
+ const url = getOrCreateBlobUrl(contentKey, content, mimeType || "image/png");
333
+ imageDebug.load(url, "blob");
334
+ imageDebug.state("loaded", { size, mimeType, contentKey });
335
+ setSrc(url);
336
+ }, [content, mimeType, hasContent, size, directSrc]);
337
+ react.useEffect(() => {
338
+ if (!src || !useProgressiveLoading) {
339
+ setLqip(null);
340
+ setIsFullyLoaded(true);
341
+ return;
342
+ }
343
+ setIsFullyLoaded(false);
344
+ imageDebug.state("progressive loading", { size });
345
+ createLQIP(src).then((placeholder) => {
346
+ if (placeholder) {
347
+ imageDebug.debug("LQIP created");
348
+ setLqip(placeholder);
349
+ }
350
+ });
351
+ const img = new Image();
352
+ img.onload = () => {
353
+ imageDebug.state("fully loaded");
354
+ setIsFullyLoaded(true);
355
+ };
356
+ img.onerror = () => {
357
+ imageDebug.error("Failed to load full image");
358
+ };
359
+ img.src = src;
360
+ }, [src, useProgressiveLoading, size]);
361
+ return {
362
+ src,
363
+ lqip,
364
+ isFullyLoaded,
365
+ useProgressiveLoading,
366
+ error,
367
+ contentKey: contentKeyRef.current,
368
+ size,
369
+ hasContent
370
+ };
371
+ }
372
+ chunkWGEGR3DF_cjs.__name(useImageLoading, "useImageLoading");
373
+ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
374
+ const t = i18n.useTypedT();
375
+ const [scale, setScale] = react.useState(1);
376
+ const [dialogOpen, setDialogOpen] = react.useState(false);
377
+ const [loadError, setLoadError] = react.useState(false);
378
+ const containerRef = react.useRef(null);
379
+ const controlsRef = react.useRef(null);
380
+ const labels = react.useMemo(() => ({
381
+ noImage: t("tools.image.noImage"),
382
+ failedToLoad: t("tools.image.failedToLoad"),
383
+ loading: t("ui.form.loading")
384
+ }), [t]);
385
+ const {
386
+ src,
387
+ lqip,
388
+ isFullyLoaded,
389
+ useProgressiveLoading,
390
+ error,
391
+ hasContent
392
+ } = useImageLoading({ content, mimeType: file.mimeType, src: directSrc });
393
+ react.useEffect(() => {
394
+ setLoadError(false);
395
+ }, [src]);
396
+ const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
397
+ resetKey: file.path
398
+ });
399
+ const handleZoomPreset = react.useCallback((value) => {
400
+ if (!controlsRef.current) return;
401
+ if (value === "fit") {
402
+ controlsRef.current.resetTransform();
403
+ } else {
404
+ controlsRef.current.setTransform(0, 0, value);
405
+ }
406
+ }, []);
407
+ const handleExpand = react.useCallback(() => {
408
+ setDialogOpen(true);
409
+ }, []);
410
+ react.useEffect(() => {
411
+ const handleKeyDown = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
412
+ if (!containerRef.current?.contains(document.activeElement) && document.activeElement !== containerRef.current) {
413
+ return;
414
+ }
415
+ const controls = controlsRef.current;
416
+ if (!controls) return;
417
+ switch (e.key) {
418
+ case "+":
419
+ case "=":
420
+ e.preventDefault();
421
+ controls.zoomIn();
422
+ break;
423
+ case "-":
424
+ e.preventDefault();
425
+ controls.zoomOut();
426
+ break;
427
+ case "0":
428
+ e.preventDefault();
429
+ controls.resetTransform();
430
+ break;
431
+ case "r":
432
+ if (!e.metaKey && !e.ctrlKey) {
433
+ e.preventDefault();
434
+ rotate();
435
+ }
436
+ break;
437
+ }
438
+ }, "handleKeyDown");
439
+ window.addEventListener("keydown", handleKeyDown);
440
+ return () => window.removeEventListener("keydown", handleKeyDown);
441
+ }, [rotate]);
442
+ if (error || loadError) {
443
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 bg-muted/30 p-4", children: [
444
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-12 h-12 text-destructive/70" }),
445
+ /* @__PURE__ */ jsxRuntime.jsxs(uiCore.Alert, { variant: "destructive", className: "max-w-md", children: [
446
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4" }),
447
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.AlertDescription, { children: error || labels.failedToLoad })
448
+ ] })
449
+ ] });
450
+ }
451
+ if (!hasContent) {
452
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30", children: [
453
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImageIcon, { className: "w-12 h-12 text-muted-foreground/50" }),
454
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.noImage })
455
+ ] });
456
+ }
457
+ return /* @__PURE__ */ jsxRuntime.jsxs(
458
+ "div",
459
+ {
460
+ ref: containerRef,
461
+ tabIndex: 0,
462
+ className: uiCore.cn(
463
+ "flex-1 h-full relative overflow-hidden outline-none",
464
+ "bg-[length:16px_16px]",
465
+ "[background-color:hsl(var(--muted)/0.2)]",
466
+ "[background-image:linear-gradient(45deg,hsl(var(--muted)/0.4)_25%,transparent_25%),linear-gradient(-45deg,hsl(var(--muted)/0.4)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,hsl(var(--muted)/0.4)_75%),linear-gradient(-45deg,transparent_75%,hsl(var(--muted)/0.4)_75%)]",
467
+ "[background-position:0_0,0_8px,8px_-8px,-8px_0px]"
468
+ ),
469
+ children: [
470
+ src && /* @__PURE__ */ jsxRuntime.jsx(ImageInfo, { src }),
471
+ useProgressiveLoading && !isFullyLoaded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-3 left-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono flex items-center gap-1.5", children: [
472
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-pulse" }),
473
+ labels.loading
474
+ ] }),
475
+ /* @__PURE__ */ jsxRuntime.jsxs(
476
+ reactZoomPanPinch.TransformWrapper,
477
+ {
478
+ initialScale: 1,
479
+ minScale: 0.1,
480
+ maxScale: 8,
481
+ centerOnInit: true,
482
+ centerZoomedOut: true,
483
+ onTransformed: (ref, state) => {
484
+ setScale(state.scale);
485
+ controlsRef.current = ref;
486
+ },
487
+ onInit: (ref) => {
488
+ controlsRef.current = ref;
489
+ },
490
+ wheel: { step: 0.1 },
491
+ doubleClick: { mode: "toggle", step: 2 },
492
+ panning: { velocityDisabled: false },
493
+ children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(
495
+ ImageToolbar,
496
+ {
497
+ scale,
498
+ transform,
499
+ onRotate: rotate,
500
+ onFlipH: flipH,
501
+ onFlipV: flipV,
502
+ onZoomPreset: handleZoomPreset,
503
+ onExpand: !inDialog ? handleExpand : void 0
504
+ }
505
+ ),
506
+ /* @__PURE__ */ jsxRuntime.jsx(
507
+ reactZoomPanPinch.TransformComponent,
508
+ {
509
+ wrapperClass: "!w-full !h-full cursor-grab active:cursor-grabbing",
510
+ contentClass: "!w-full !h-full flex items-center justify-center",
511
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
512
+ useProgressiveLoading && lqip && !isFullyLoaded && /* @__PURE__ */ jsxRuntime.jsx(
513
+ "img",
514
+ {
515
+ src: lqip,
516
+ alt: "",
517
+ "aria-hidden": "true",
518
+ className: "absolute inset-0 max-w-full max-h-full object-contain select-none",
519
+ style: {
520
+ transform: transformStyle,
521
+ filter: "blur(20px)",
522
+ transition: "opacity 0.3s ease-out",
523
+ opacity: isFullyLoaded ? 0 : 1
524
+ },
525
+ draggable: false
526
+ }
527
+ ),
528
+ src && /* @__PURE__ */ jsxRuntime.jsx(
529
+ "img",
530
+ {
531
+ src,
532
+ alt: file.name,
533
+ className: "max-w-full max-h-full object-contain select-none",
534
+ style: {
535
+ transform: transformStyle,
536
+ transition: useProgressiveLoading ? "transform 0.15s ease-out, opacity 0.3s ease-out" : "transform 0.15s ease-out",
537
+ opacity: useProgressiveLoading && !isFullyLoaded ? 0 : 1
538
+ },
539
+ draggable: false,
540
+ crossOrigin: "anonymous",
541
+ onError: () => setLoadError(true)
542
+ }
543
+ )
544
+ ] })
545
+ }
546
+ )
547
+ ]
548
+ }
549
+ ),
550
+ !inDialog && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(uiCore.DialogContent, { className: "max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh] p-0 overflow-hidden [&>button]:hidden flex flex-col", children: [
551
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.DialogTitle, { className: "sr-only", children: file.name }),
552
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-4 py-2 border-b shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: file.name }) }),
553
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(ImageViewer, { file, content, src: directSrc, inDialog: true }) })
554
+ ] }) })
555
+ ]
556
+ }
557
+ );
558
+ }
559
+ chunkWGEGR3DF_cjs.__name(ImageViewer, "ImageViewer");
560
+
561
+ exports.ImageInfo = ImageInfo;
562
+ exports.ImageToolbar = ImageToolbar;
563
+ exports.ImageViewer = ImageViewer;
564
+ //# sourceMappingURL=chunk-TEFRA7GW.cjs.map
565
+ //# sourceMappingURL=chunk-TEFRA7GW.cjs.map