react-file-preview-engine 0.1.7 → 0.1.8
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 +6 -4
- package/dist/chunks/chunk-YEB7RW6X.js +238 -0
- package/dist/components.d.ts +6 -0
- package/dist/components.js +1 -0
- package/dist/index.d.ts +3 -6
- package/dist/index.js +1 -238
- package/dist/types.d.ts +2 -2
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -302,7 +302,9 @@ You can explore the actual renderer definitions in the [`defaultRenderers` const
|
|
|
302
302
|
|
|
303
303
|
## API Reference
|
|
304
304
|
|
|
305
|
-
### FilePreviewer
|
|
305
|
+
### FilePreviewer Component
|
|
306
|
+
|
|
307
|
+
Here is the full API for the `<FilePreviewer>` component, these properties can be set on an instance of FilePreviewer:
|
|
306
308
|
|
|
307
309
|
| Prop | Type | Required | Default | Description |
|
|
308
310
|
| ------------------- | ------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
@@ -324,7 +326,7 @@ You can explore the actual renderer definitions in the [`defaultRenderers` const
|
|
|
324
326
|
### DivProps
|
|
325
327
|
|
|
326
328
|
```typescript
|
|
327
|
-
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
329
|
+
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
328
330
|
|
|
329
331
|
type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
330
332
|
```
|
|
@@ -360,6 +362,6 @@ type Renderer<T extends object = {}> = {
|
|
|
360
362
|
};
|
|
361
363
|
```
|
|
362
364
|
|
|
363
|
-
##
|
|
365
|
+
## License
|
|
364
366
|
|
|
365
|
-
[
|
|
367
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { FileIcon, defaultStyles } from 'react-file-icon';
|
|
3
|
+
import Mime from 'mime/lite';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __defProps = Object.defineProperties;
|
|
7
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __spreadValues = (a, b) => {
|
|
13
|
+
for (var prop in b || (b = {}))
|
|
14
|
+
if (__hasOwnProp.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
if (__getOwnPropSymbols)
|
|
17
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
18
|
+
if (__propIsEnum.call(b, prop))
|
|
19
|
+
__defNormalProp(a, prop, b[prop]);
|
|
20
|
+
}
|
|
21
|
+
return a;
|
|
22
|
+
};
|
|
23
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
24
|
+
var __async = (__this, __arguments, generator) => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
var fulfilled = (value) => {
|
|
27
|
+
try {
|
|
28
|
+
step(generator.next(value));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
reject(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var rejected = (value) => {
|
|
34
|
+
try {
|
|
35
|
+
step(generator.throw(value));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
reject(e);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
41
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/constants.ts
|
|
46
|
+
var defaults = { additionalContext: {}, customRenderers: [], props: {} };
|
|
47
|
+
var fileExtensionRegex = /\.([^.]+)$/;
|
|
48
|
+
function useResolvedSrc(src) {
|
|
49
|
+
const objectUrlRef = useRef(null);
|
|
50
|
+
const revokeObjectURL = () => {
|
|
51
|
+
if (!objectUrlRef.current) return;
|
|
52
|
+
URL.revokeObjectURL(objectUrlRef.current);
|
|
53
|
+
objectUrlRef.current = null;
|
|
54
|
+
};
|
|
55
|
+
const resolvedSrc = useMemo(() => {
|
|
56
|
+
revokeObjectURL();
|
|
57
|
+
if (typeof src === "string") return src;
|
|
58
|
+
const url = src instanceof File || src instanceof Blob ? URL.createObjectURL(src) : src instanceof ArrayBuffer ? URL.createObjectURL(new Blob([src])) : "";
|
|
59
|
+
if (url) objectUrlRef.current = url;
|
|
60
|
+
return url;
|
|
61
|
+
}, [src]);
|
|
62
|
+
useEffect(() => revokeObjectURL, []);
|
|
63
|
+
return resolvedSrc;
|
|
64
|
+
}
|
|
65
|
+
var composeClass = (baseClass, props) => `${baseClass}${(props == null ? void 0 : props.className) ? " " + props.className : ""}`;
|
|
66
|
+
var composeProps = (baseClass, props, overrideProps) => {
|
|
67
|
+
const mergedProps = __spreadValues(__spreadValues({}, props), overrideProps);
|
|
68
|
+
return __spreadProps(__spreadValues({}, mergedProps), {
|
|
69
|
+
style: __spreadValues(__spreadValues({}, props == null ? void 0 : props.style), overrideProps == null ? void 0 : overrideProps.style),
|
|
70
|
+
className: composeClass(baseClass, mergedProps)
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
function fetchResource(src, type, signal) {
|
|
74
|
+
return __async(this, null, function* () {
|
|
75
|
+
const res = yield fetch(src, { signal });
|
|
76
|
+
if (!res.ok) throw new Error();
|
|
77
|
+
return res[type]();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
var getFileExtension = (mimeType, fileName) => {
|
|
81
|
+
var _a;
|
|
82
|
+
return Mime.getExtension(mimeType) || ((_a = fileName == null ? void 0 : fileName.match(fileExtensionRegex)) == null ? void 0 : _a[1]) || "";
|
|
83
|
+
};
|
|
84
|
+
var getFileType = (mimeType, fileName) => mimeType || fileName && Mime.getType(fileName) || "";
|
|
85
|
+
|
|
86
|
+
// src/lib/rendererRegistry.tsx
|
|
87
|
+
var audioRenderer = {
|
|
88
|
+
canRender: ({ mimeType }) => mimeType.startsWith("audio/"),
|
|
89
|
+
Component({ src, mimeType, fileName, autoPlay, onLoad, onError }) {
|
|
90
|
+
return /* @__PURE__ */ React.createElement("audio", { className: "rfpe-audio", controls: true, autoPlay, onCanPlay: onLoad, onError, "aria-label": fileName || "Audio preview" }, /* @__PURE__ */ React.createElement("source", { src, type: mimeType }));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var fallbackRenderer = {
|
|
94
|
+
Component({ mimeType, fileName, iconProps, onLoad }) {
|
|
95
|
+
const extension = useMemo(() => getFileExtension(mimeType, fileName), [mimeType, fileName]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
onLoad();
|
|
98
|
+
}, []);
|
|
99
|
+
return /* @__PURE__ */ React.createElement("div", __spreadValues({}, composeProps("rfpe-icon", iconProps)), /* @__PURE__ */ React.createElement(FileIcon, __spreadValues({ extension }, defaultStyles[extension])));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var htmlRenderer = {
|
|
103
|
+
canRender: ({ mimeType }) => mimeType === "text/html",
|
|
104
|
+
Component({ src, onLoad, onError }) {
|
|
105
|
+
const [data, setData] = useState("");
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
fetchResource(src, "text", controller.signal).then((data2) => {
|
|
109
|
+
setData(data2);
|
|
110
|
+
onLoad();
|
|
111
|
+
}).catch(onError);
|
|
112
|
+
return () => controller.abort();
|
|
113
|
+
}, [src]);
|
|
114
|
+
return /* @__PURE__ */ React.createElement("iframe", { className: "rfpe-iframe", src: `data:text/html; charset=utf-8,${encodeURIComponent(data)}`, sandbox: "" });
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var imageRenderer = {
|
|
118
|
+
canRender: ({ mimeType }) => mimeType.startsWith("image/"),
|
|
119
|
+
Component({ src, fileName, onLoad, onError }) {
|
|
120
|
+
return /* @__PURE__ */ React.createElement("img", { className: "rfpe-image", src, alt: fileName || "Image preview", onLoad, onError });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var pdfRenderer = {
|
|
124
|
+
canRender: ({ mimeType }) => mimeType === "application/pdf",
|
|
125
|
+
Component({ src, onLoad, onError }) {
|
|
126
|
+
const [data, setData] = useState("");
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
let objectUrl;
|
|
130
|
+
fetchResource(src, "arrayBuffer", controller.signal).then((buffer) => {
|
|
131
|
+
const blob = new Blob([buffer], { type: "application/pdf" });
|
|
132
|
+
objectUrl = URL.createObjectURL(blob);
|
|
133
|
+
setData(objectUrl);
|
|
134
|
+
onLoad();
|
|
135
|
+
}).catch(onError);
|
|
136
|
+
return () => {
|
|
137
|
+
controller.abort();
|
|
138
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
139
|
+
};
|
|
140
|
+
}, [src]);
|
|
141
|
+
return /* @__PURE__ */ React.createElement("iframe", { className: "rfpe-iframe", src: data });
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var textRenderer = {
|
|
145
|
+
canRender: ({ mimeType }) => mimeType === "text/plain",
|
|
146
|
+
Component({ src, onLoad, onError }) {
|
|
147
|
+
const [data, setData] = useState("");
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const controller = new AbortController();
|
|
150
|
+
fetchResource(src, "text", controller.signal).then((data2) => {
|
|
151
|
+
setData(data2);
|
|
152
|
+
onLoad();
|
|
153
|
+
}).catch(onError);
|
|
154
|
+
return () => controller.abort();
|
|
155
|
+
}, [src]);
|
|
156
|
+
return /* @__PURE__ */ React.createElement("div", { className: "rfpe-text" }, data);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var videoRenderer = {
|
|
160
|
+
canRender: ({ mimeType }) => mimeType.startsWith("video/"),
|
|
161
|
+
Component({ src, mimeType, fileName, autoPlay, onLoad, onError }) {
|
|
162
|
+
return /* @__PURE__ */ React.createElement("video", { className: "rfpe-video", controls: true, autoPlay, onCanPlay: onLoad, onError, "aria-label": fileName || "Video preview" }, /* @__PURE__ */ React.createElement("source", { src, type: mimeType }));
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var defaultRenderers = [textRenderer, pdfRenderer, htmlRenderer, imageRenderer, audioRenderer, videoRenderer];
|
|
166
|
+
function resolveRenderer(customRenderers, ctx) {
|
|
167
|
+
var _a;
|
|
168
|
+
return (_a = customRenderers.concat(defaultRenderers).find((r) => {
|
|
169
|
+
var _a2;
|
|
170
|
+
return (_a2 = r.canRender) == null ? void 0 : _a2.call(r, ctx);
|
|
171
|
+
})) != null ? _a : fallbackRenderer;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/styles.css
|
|
175
|
+
function injectStyle(css) {
|
|
176
|
+
if (typeof document === "undefined") return;
|
|
177
|
+
const head = document.head || document.getElementsByTagName("head")[0];
|
|
178
|
+
const style = document.createElement("style");
|
|
179
|
+
style.type = "text/css";
|
|
180
|
+
if (head.firstChild) {
|
|
181
|
+
head.insertBefore(style, head.firstChild);
|
|
182
|
+
} else {
|
|
183
|
+
head.appendChild(style);
|
|
184
|
+
}
|
|
185
|
+
if (style.styleSheet) {
|
|
186
|
+
style.styleSheet.cssText = css;
|
|
187
|
+
} else {
|
|
188
|
+
style.appendChild(document.createTextNode(css));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
injectStyle("@layer rfpe {\n :where(.rfpe-container) {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n max-width: 100lvw;\n max-width: 100vw;\n }\n :where(.rfpe-icon) {\n width: 50px;\n }\n}\n.rfpe-audio {\n width: 100%;\n}\n.rfpe-iframe {\n background-color: white;\n}\n.rfpe-iframe,\n.rfpe-image,\n.rfpe-video {\n width: 100%;\n height: 100%;\n}\n.rfpe-image,\n.rfpe-video {\n object-fit: contain;\n}\n.rfpe-text {\n width: 100%;\n height: 100%;\n overflow: auto;\n white-space: pre-wrap;\n}\n.rfpe-loader {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n}\n.rfpe-loader-spinner {\n box-sizing: border-box;\n width: 22px;\n aspect-ratio: 1;\n border-radius: 50%;\n border: 2px solid transparent;\n border-top-color: black;\n border-bottom-color: black;\n animation: rfpe-spin-fast 0.6s ease infinite;\n}\n.rfpe-loader-text {\n font-size: 0.875rem;\n}\n@keyframes rfpe-spin-fast {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
192
|
+
|
|
193
|
+
// src/components.tsx
|
|
194
|
+
var { additionalContext: defaultContext, customRenderers: defaultRenderers2, props: defaultProps } = defaults;
|
|
195
|
+
function FilePreviewer({
|
|
196
|
+
src,
|
|
197
|
+
mimeType,
|
|
198
|
+
fileName = "",
|
|
199
|
+
autoPlay = false,
|
|
200
|
+
loader = /* @__PURE__ */ React.createElement(Loader, null),
|
|
201
|
+
customRenderers = defaultRenderers2,
|
|
202
|
+
additionalContext = defaultContext,
|
|
203
|
+
errorRenderer = fallbackRenderer,
|
|
204
|
+
containerProps = defaultProps,
|
|
205
|
+
iconProps = defaultProps,
|
|
206
|
+
onLoad,
|
|
207
|
+
onError
|
|
208
|
+
}) {
|
|
209
|
+
const resolvedSrc = useResolvedSrc(src);
|
|
210
|
+
const fileType = useMemo(() => getFileType(mimeType, fileName), [mimeType, fileName]);
|
|
211
|
+
const fileKey = `${resolvedSrc}|${fileType}|${fileName}`;
|
|
212
|
+
const [state, setState] = useState({ key: fileKey, status: "loading" });
|
|
213
|
+
if (state.key !== fileKey) setState({ key: fileKey, status: "loading" });
|
|
214
|
+
const isLoading = state.status === "loading";
|
|
215
|
+
const handleLoad = () => {
|
|
216
|
+
setState((prev) => {
|
|
217
|
+
if (prev.key !== fileKey || prev.status !== "loading") return prev;
|
|
218
|
+
onLoad == null ? void 0 : onLoad();
|
|
219
|
+
return { key: fileKey, status: "ready" };
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
const handleError = () => {
|
|
223
|
+
setState((prev) => {
|
|
224
|
+
if (prev.key !== fileKey || prev.status === "error") return prev;
|
|
225
|
+
onError == null ? void 0 : onError();
|
|
226
|
+
return { key: fileKey, status: "error" };
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
const context = __spreadValues({ src: resolvedSrc, mimeType: fileType, fileName, autoPlay, iconProps, onLoad: handleLoad, onError: handleError }, additionalContext);
|
|
230
|
+
const renderer = useMemo(() => resolveRenderer(customRenderers, context), [resolvedSrc, fileType, fileName, autoPlay, additionalContext]);
|
|
231
|
+
const ActiveRenderer = state.status === "error" ? errorRenderer : renderer;
|
|
232
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, isLoading && loader, /* @__PURE__ */ React.createElement("div", __spreadValues({}, composeProps("rfpe-container", containerProps, { style: { visibility: isLoading ? "hidden" : "visible" } })), /* @__PURE__ */ React.createElement(ActiveRenderer.Component, __spreadValues({ key: fileKey }, context))));
|
|
233
|
+
}
|
|
234
|
+
function Loader({ children, text }) {
|
|
235
|
+
return /* @__PURE__ */ React.createElement("div", { className: "rfpe-loader" }, /* @__PURE__ */ React.createElement("div", { className: "rfpe-loader-spinner" }), children != null ? children : /* @__PURE__ */ React.createElement("div", { className: "rfpe-loader-text" }, text));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { FilePreviewer };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FilePreviewerProps } from './types.js';
|
|
3
|
+
|
|
4
|
+
declare function FilePreviewer<T extends object = {}>({ src, mimeType, fileName, autoPlay, loader, customRenderers, additionalContext, errorRenderer, containerProps, iconProps, onLoad, onError, }: FilePreviewerProps<T>): React.JSX.Element;
|
|
5
|
+
|
|
6
|
+
export { FilePreviewer as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FilePreviewer as default } from './chunks/chunk-YEB7RW6X.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
declare function FilePreviewer<T extends object = {}>({ src, mimeType, fileName, autoPlay, loader, customRenderers, additionalContext, errorRenderer, containerProps, iconProps, onLoad, onError, }: FilePreviewerProps<T>): React.JSX.Element;
|
|
5
|
-
|
|
6
|
-
export { FilePreviewer as default };
|
|
1
|
+
export { default } from './components.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
import './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,238 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { FileIcon, defaultStyles } from 'react-file-icon';
|
|
3
|
-
import Mime from 'mime/lite';
|
|
4
|
-
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __defProps = Object.defineProperties;
|
|
7
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
8
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
-
var __spreadValues = (a, b) => {
|
|
13
|
-
for (var prop in b || (b = {}))
|
|
14
|
-
if (__hasOwnProp.call(b, prop))
|
|
15
|
-
__defNormalProp(a, prop, b[prop]);
|
|
16
|
-
if (__getOwnPropSymbols)
|
|
17
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
18
|
-
if (__propIsEnum.call(b, prop))
|
|
19
|
-
__defNormalProp(a, prop, b[prop]);
|
|
20
|
-
}
|
|
21
|
-
return a;
|
|
22
|
-
};
|
|
23
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
24
|
-
var __async = (__this, __arguments, generator) => {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
var fulfilled = (value) => {
|
|
27
|
-
try {
|
|
28
|
-
step(generator.next(value));
|
|
29
|
-
} catch (e) {
|
|
30
|
-
reject(e);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
var rejected = (value) => {
|
|
34
|
-
try {
|
|
35
|
-
step(generator.throw(value));
|
|
36
|
-
} catch (e) {
|
|
37
|
-
reject(e);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
41
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
function Loader({ children, text }) {
|
|
45
|
-
return /* @__PURE__ */ React2.createElement("div", { className: "rfpe-loader" }, /* @__PURE__ */ React2.createElement("div", { className: "rfpe-loader-spinner" }), children != null ? children : /* @__PURE__ */ React2.createElement("div", { className: "rfpe-loader-text" }, text));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/constants.ts
|
|
49
|
-
var defaults = { additionalContext: {}, customRenderers: [], props: {} };
|
|
50
|
-
var fileExtensionRegex = /\.([^.]+)$/;
|
|
51
|
-
function useResolvedSrc(src) {
|
|
52
|
-
const objectUrlRef = useRef(null);
|
|
53
|
-
const revokeObjectURL = () => {
|
|
54
|
-
if (!objectUrlRef.current) return;
|
|
55
|
-
URL.revokeObjectURL(objectUrlRef.current);
|
|
56
|
-
objectUrlRef.current = null;
|
|
57
|
-
};
|
|
58
|
-
const resolvedSrc = useMemo(() => {
|
|
59
|
-
revokeObjectURL();
|
|
60
|
-
if (typeof src === "string") return src;
|
|
61
|
-
const url = src instanceof File || src instanceof Blob ? URL.createObjectURL(src) : src instanceof ArrayBuffer ? URL.createObjectURL(new Blob([src])) : "";
|
|
62
|
-
if (url) objectUrlRef.current = url;
|
|
63
|
-
return url;
|
|
64
|
-
}, [src]);
|
|
65
|
-
useEffect(() => revokeObjectURL, []);
|
|
66
|
-
return resolvedSrc;
|
|
67
|
-
}
|
|
68
|
-
var composeClass = (baseClass, props) => `${baseClass}${(props == null ? void 0 : props.className) ? " " + props.className : ""}`;
|
|
69
|
-
var composeProps = (baseClass, props, overrideProps) => {
|
|
70
|
-
const mergedProps = __spreadValues(__spreadValues({}, props), overrideProps);
|
|
71
|
-
return __spreadProps(__spreadValues({}, mergedProps), {
|
|
72
|
-
style: __spreadValues(__spreadValues({}, props == null ? void 0 : props.style), overrideProps == null ? void 0 : overrideProps.style),
|
|
73
|
-
className: composeClass(baseClass, mergedProps)
|
|
74
|
-
});
|
|
75
|
-
};
|
|
76
|
-
function fetchResource(src, type, signal) {
|
|
77
|
-
return __async(this, null, function* () {
|
|
78
|
-
const res = yield fetch(src, { signal });
|
|
79
|
-
if (!res.ok) throw new Error();
|
|
80
|
-
return res[type]();
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
var getFileExtension = (mimeType, fileName) => {
|
|
84
|
-
var _a;
|
|
85
|
-
return Mime.getExtension(mimeType) || ((_a = fileName == null ? void 0 : fileName.match(fileExtensionRegex)) == null ? void 0 : _a[1]) || "";
|
|
86
|
-
};
|
|
87
|
-
var getFileType = (mimeType, fileName) => mimeType || fileName && Mime.getType(fileName) || "";
|
|
88
|
-
|
|
89
|
-
// src/lib/rendererRegistry.tsx
|
|
90
|
-
var audioRenderer = {
|
|
91
|
-
canRender: ({ mimeType }) => mimeType.startsWith("audio/"),
|
|
92
|
-
Component({ src, mimeType, fileName, autoPlay, onLoad, onError }) {
|
|
93
|
-
return /* @__PURE__ */ React2.createElement("audio", { className: "rfpe-audio", controls: true, autoPlay, onCanPlay: onLoad, onError, "aria-label": fileName || "Audio preview" }, /* @__PURE__ */ React2.createElement("source", { src, type: mimeType }));
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
var fallbackRenderer = {
|
|
97
|
-
Component({ mimeType, fileName, iconProps, onLoad }) {
|
|
98
|
-
const extension = useMemo(() => getFileExtension(mimeType, fileName), [mimeType, fileName]);
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
onLoad();
|
|
101
|
-
}, []);
|
|
102
|
-
return /* @__PURE__ */ React2.createElement("div", __spreadValues({}, composeProps("rfpe-icon", iconProps)), /* @__PURE__ */ React2.createElement(FileIcon, __spreadValues({ extension }, defaultStyles[extension])));
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
var htmlRenderer = {
|
|
106
|
-
canRender: ({ mimeType }) => mimeType === "text/html",
|
|
107
|
-
Component({ src, onLoad, onError }) {
|
|
108
|
-
const [data, setData] = useState("");
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
const controller = new AbortController();
|
|
111
|
-
fetchResource(src, "text", controller.signal).then((data2) => {
|
|
112
|
-
setData(data2);
|
|
113
|
-
onLoad();
|
|
114
|
-
}).catch(onError);
|
|
115
|
-
return () => controller.abort();
|
|
116
|
-
}, [src]);
|
|
117
|
-
return /* @__PURE__ */ React2.createElement("iframe", { className: "rfpe-iframe", src: `data:text/html; charset=utf-8,${encodeURIComponent(data)}`, sandbox: "" });
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
var imageRenderer = {
|
|
121
|
-
canRender: ({ mimeType }) => mimeType.startsWith("image/"),
|
|
122
|
-
Component({ src, fileName, onLoad, onError }) {
|
|
123
|
-
return /* @__PURE__ */ React2.createElement("img", { className: "rfpe-image", src, alt: fileName || "Image preview", onLoad, onError });
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
var pdfRenderer = {
|
|
127
|
-
canRender: ({ mimeType }) => mimeType === "application/pdf",
|
|
128
|
-
Component({ src, onLoad, onError }) {
|
|
129
|
-
const [data, setData] = useState("");
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
const controller = new AbortController();
|
|
132
|
-
let objectUrl;
|
|
133
|
-
fetchResource(src, "arrayBuffer", controller.signal).then((buffer) => {
|
|
134
|
-
const blob = new Blob([buffer], { type: "application/pdf" });
|
|
135
|
-
objectUrl = URL.createObjectURL(blob);
|
|
136
|
-
setData(objectUrl);
|
|
137
|
-
onLoad();
|
|
138
|
-
}).catch(onError);
|
|
139
|
-
return () => {
|
|
140
|
-
controller.abort();
|
|
141
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
142
|
-
};
|
|
143
|
-
}, [src]);
|
|
144
|
-
return /* @__PURE__ */ React2.createElement("iframe", { className: "rfpe-iframe", src: data });
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
var textRenderer = {
|
|
148
|
-
canRender: ({ mimeType }) => mimeType === "text/plain",
|
|
149
|
-
Component({ src, onLoad, onError }) {
|
|
150
|
-
const [data, setData] = useState("");
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
const controller = new AbortController();
|
|
153
|
-
fetchResource(src, "text", controller.signal).then((data2) => {
|
|
154
|
-
setData(data2);
|
|
155
|
-
onLoad();
|
|
156
|
-
}).catch(onError);
|
|
157
|
-
return () => controller.abort();
|
|
158
|
-
}, [src]);
|
|
159
|
-
return /* @__PURE__ */ React2.createElement("div", { className: "rfpe-text" }, data);
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
var videoRenderer = {
|
|
163
|
-
canRender: ({ mimeType }) => mimeType.startsWith("video/"),
|
|
164
|
-
Component({ src, mimeType, fileName, autoPlay, onLoad, onError }) {
|
|
165
|
-
return /* @__PURE__ */ React2.createElement("video", { className: "rfpe-video", controls: true, autoPlay, onCanPlay: onLoad, onError, "aria-label": fileName || "Video preview" }, /* @__PURE__ */ React2.createElement("source", { src, type: mimeType }));
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
var defaultRenderers = [textRenderer, pdfRenderer, htmlRenderer, imageRenderer, audioRenderer, videoRenderer];
|
|
169
|
-
function resolveRenderer(customRenderers, ctx) {
|
|
170
|
-
var _a;
|
|
171
|
-
return (_a = customRenderers.concat(defaultRenderers).find((r) => {
|
|
172
|
-
var _a2;
|
|
173
|
-
return (_a2 = r.canRender) == null ? void 0 : _a2.call(r, ctx);
|
|
174
|
-
})) != null ? _a : fallbackRenderer;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// src/styles.css
|
|
178
|
-
function injectStyle(css) {
|
|
179
|
-
if (typeof document === "undefined") return;
|
|
180
|
-
const head = document.head || document.getElementsByTagName("head")[0];
|
|
181
|
-
const style = document.createElement("style");
|
|
182
|
-
style.type = "text/css";
|
|
183
|
-
if (head.firstChild) {
|
|
184
|
-
head.insertBefore(style, head.firstChild);
|
|
185
|
-
} else {
|
|
186
|
-
head.appendChild(style);
|
|
187
|
-
}
|
|
188
|
-
if (style.styleSheet) {
|
|
189
|
-
style.styleSheet.cssText = css;
|
|
190
|
-
} else {
|
|
191
|
-
style.appendChild(document.createTextNode(css));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
injectStyle("@layer rfpe {\n :where(.rfpe-container) {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n max-width: 100lvw;\n max-width: 100vw;\n }\n :where(.rfpe-icon) {\n width: 50px;\n }\n}\n.rfpe-audio {\n width: 100%;\n}\n.rfpe-iframe {\n background-color: white;\n}\n.rfpe-iframe,\n.rfpe-image,\n.rfpe-video {\n width: 100%;\n height: 100%;\n}\n.rfpe-image,\n.rfpe-video {\n object-fit: contain;\n}\n.rfpe-text {\n width: 100%;\n height: 100%;\n overflow: auto;\n white-space: pre-wrap;\n}\n.rfpe-loader {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n}\n.rfpe-loader-spinner {\n box-sizing: border-box;\n width: 22px;\n aspect-ratio: 1;\n border-radius: 50%;\n border: 2px solid transparent;\n border-top-color: black;\n border-bottom-color: black;\n animation: rfpe-spin-fast 0.6s ease infinite;\n}\n.rfpe-loader-text {\n font-size: 0.875rem;\n}\n@keyframes rfpe-spin-fast {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
195
|
-
|
|
196
|
-
// src/index.tsx
|
|
197
|
-
var { additionalContext: defaultContext, customRenderers: defaultRenderers2, props: defaultProps } = defaults;
|
|
198
|
-
function FilePreviewer({
|
|
199
|
-
src,
|
|
200
|
-
mimeType,
|
|
201
|
-
fileName = "",
|
|
202
|
-
autoPlay = false,
|
|
203
|
-
loader = /* @__PURE__ */ React2.createElement(Loader, null),
|
|
204
|
-
customRenderers = defaultRenderers2,
|
|
205
|
-
additionalContext = defaultContext,
|
|
206
|
-
errorRenderer = fallbackRenderer,
|
|
207
|
-
containerProps = defaultProps,
|
|
208
|
-
iconProps = defaultProps,
|
|
209
|
-
onLoad,
|
|
210
|
-
onError
|
|
211
|
-
}) {
|
|
212
|
-
const resolvedSrc = useResolvedSrc(src);
|
|
213
|
-
const fileType = useMemo(() => getFileType(mimeType, fileName), [mimeType, fileName]);
|
|
214
|
-
const fileKey = `${resolvedSrc}|${fileType}|${fileName}`;
|
|
215
|
-
const [state, setState] = useState({ key: fileKey, status: "loading" });
|
|
216
|
-
if (state.key !== fileKey) setState({ key: fileKey, status: "loading" });
|
|
217
|
-
const isLoading = state.status === "loading";
|
|
218
|
-
const handleLoad = () => {
|
|
219
|
-
setState((prev) => {
|
|
220
|
-
if (prev.key !== fileKey || prev.status !== "loading") return prev;
|
|
221
|
-
onLoad == null ? void 0 : onLoad();
|
|
222
|
-
return { key: fileKey, status: "ready" };
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
const handleError = () => {
|
|
226
|
-
setState((prev) => {
|
|
227
|
-
if (prev.key !== fileKey || prev.status === "error") return prev;
|
|
228
|
-
onError == null ? void 0 : onError();
|
|
229
|
-
return { key: fileKey, status: "error" };
|
|
230
|
-
});
|
|
231
|
-
};
|
|
232
|
-
const context = __spreadValues({ src: resolvedSrc, mimeType: fileType, fileName, autoPlay, iconProps, onLoad: handleLoad, onError: handleError }, additionalContext);
|
|
233
|
-
const renderer = useMemo(() => resolveRenderer(customRenderers, context), [resolvedSrc, fileType, fileName, autoPlay, additionalContext]);
|
|
234
|
-
const ActiveRenderer = state.status === "error" ? errorRenderer : renderer;
|
|
235
|
-
return /* @__PURE__ */ React2.createElement(React2.Fragment, null, isLoading && loader, /* @__PURE__ */ React2.createElement("div", __spreadValues({}, composeProps("rfpe-container", containerProps, { style: { visibility: isLoading ? "hidden" : "visible" } })), /* @__PURE__ */ React2.createElement(ActiveRenderer.Component, __spreadValues({ key: fileKey }, context))));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export { FilePreviewer as default };
|
|
1
|
+
export { FilePreviewer as default } from './chunks/chunk-YEB7RW6X.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode, DetailedHTMLProps, HTMLAttributes
|
|
1
|
+
import { ReactNode, DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
2
2
|
|
|
3
3
|
type LoaderProps = {
|
|
4
4
|
children?: ReactNode;
|
|
@@ -19,7 +19,7 @@ type RenderContext<T extends object = {}> = {
|
|
|
19
19
|
type Renderer<T extends object = {}> = {
|
|
20
20
|
name?: string;
|
|
21
21
|
canRender?(ctx: RenderContext<T>): boolean;
|
|
22
|
-
Component(ctx: RenderContext<T>):
|
|
22
|
+
Component(ctx: RenderContext<T>): ReactNode;
|
|
23
23
|
};
|
|
24
24
|
type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
25
25
|
type FetchType = "text" | "arrayBuffer" | "blob" | "json";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-file-preview-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "A renderer-driven React file preview engine with extensible architecture for images, video, audio, pdf, html, and custom formats.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sahil Aggarwal <aggarwalsahil2004@gmail.com>",
|
|
@@ -14,34 +14,35 @@
|
|
|
14
14
|
"url": "https://github.com/SahilAggarwal2004/react-file-preview-engine/issues"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
|
-
"files": [
|
|
18
|
-
"dist"
|
|
19
|
-
],
|
|
20
17
|
"exports": {
|
|
21
18
|
".": "./dist/index.js",
|
|
22
19
|
"./types": "./dist/types.js"
|
|
23
20
|
},
|
|
24
21
|
"main": "dist/index.js",
|
|
25
|
-
"
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
26
25
|
"sideEffects": [
|
|
27
26
|
"**/*.css"
|
|
28
27
|
],
|
|
28
|
+
"types": "dist/index.d.ts",
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"mime": "^4.1.0",
|
|
31
31
|
"react-file-icon": "^1.6.0"
|
|
32
32
|
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
35
|
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
36
|
+
},
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@release-it/conventional-changelog": "^10.0.4",
|
|
35
39
|
"@types/react": "^19.2.7",
|
|
36
40
|
"@types/react-file-icon": "^1.0.4",
|
|
41
|
+
"prettier-package-json": "^2.8.0",
|
|
37
42
|
"release-it": "^19.2.2",
|
|
38
43
|
"tsup": "^8.5.1",
|
|
39
44
|
"typescript": "^5.9.3"
|
|
40
45
|
},
|
|
41
|
-
"peerDependencies": {
|
|
42
|
-
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
43
|
-
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
44
|
-
},
|
|
45
46
|
"keywords": [
|
|
46
47
|
"arraybuffer",
|
|
47
48
|
"audio-preview",
|
|
@@ -69,6 +70,8 @@
|
|
|
69
70
|
"compile": "tsup",
|
|
70
71
|
"dev": "tsup --watch",
|
|
71
72
|
"dry-release": "release-it --ci --dry-run",
|
|
73
|
+
"prettier": "prettier-package-json --write package.json",
|
|
74
|
+
"pub": "pnpm login && pnpm publish",
|
|
72
75
|
"release": "release-it --ci"
|
|
73
76
|
}
|
|
74
77
|
}
|