@react-md/form
Version:
This package is for creating all the different form input types.
315 lines • 13 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { useCallback, useEffect, useReducer } from "react";
import { nanoid } from "nanoid";
import { omit } from "@react-md/utils";
import { getFileParser as defaultGetFileParser, FileAccessError, isValidFileName as defaultIsValidFileName, validateFiles as defaultValidateFiles, } from "./utils";
/** @internal */
var EMPTY_LIST = [];
/** @internal */
var EMPTY_OBJECT = {};
/**
* This hook is generally used to upload files **to the browser** in different
* formats to be previewed `<img>`, `<video>`, `<embed>`, etc tags. However, it
* can also be used to upload the files as an `ArrayBuffer` and then uploaded to
* a server.
*
* Note: If using the `aws-sdk` to upload files directly to S3, **do not use
* this hook** since it uses its own upload process.
*
* @typeParam E - An optional HTMLElement type that is used for the
* {@link FileUploadHandlers}.
* @typeParam CustomError - An optional error type that gets returned from the
* {@link FilesValidator}.
* @param options - All the {@link FileUploadOptions}
* @returns the {@link FileUploadHookReturnValue}
* @remarks \@since 2.9.0
*/
export function useFileUpload(_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.maxFiles, maxFiles = _c === void 0 ? -1 : _c, _d = _b.extensions, extensions = _d === void 0 ? EMPTY_LIST : _d, _e = _b.minFileSize, minFileSize = _e === void 0 ? -1 : _e, _f = _b.maxFileSize, maxFileSize = _f === void 0 ? -1 : _f, _g = _b.totalFileSize, totalFileSize = _g === void 0 ? -1 : _g, _h = _b.concurrency, concurrency = _h === void 0 ? -1 : _h, propOnDrop = _b.onDrop, propOnChange = _b.onChange, _j = _b.validateFiles, validateFiles = _j === void 0 ? defaultValidateFiles : _j, _k = _b.getFileParser, getFileParser = _k === void 0 ? defaultGetFileParser : _k, _l = _b.isValidFileName, isValidFileName = _l === void 0 ? defaultIsValidFileName : _l;
var _m = __read(useReducer(function reducer(state, action) {
var _a, _b, _c, _d;
switch (action.type) {
case "reset":
// need to reuse constants so that calling reset doesn't cause an
// infinite loop in an effect
return {
stats: EMPTY_OBJECT,
errors: EMPTY_LIST,
readers: EMPTY_OBJECT,
};
case "remove":
return __assign(__assign({}, state), { stats: omit(state.stats, action.files) });
case "queue":
return __assign(__assign({}, state), { stats: __assign(__assign({}, state.stats), action.files.reduce(function (files, file) {
var key = nanoid();
files[key] = {
key: key,
file: file,
progress: 0,
status: "pending",
};
return files;
}, {})), errors: __spreadArray(__spreadArray([], __read(state.errors), false), __read(action.errors), false) });
case "start": {
var key = action.key, reader = action.reader;
/* istanbul ignore next */
if (!state.stats[key]) {
throw new Error("Missing file with key \"".concat(key, "\""));
}
var fileStats = {
key: key,
file: state.stats[key].file,
progress: 0,
status: "uploading",
};
return __assign(__assign({}, state), { readers: __assign(__assign({}, state.readers), (_a = {}, _a[key] = reader, _a)), stats: __assign(__assign({}, state.stats), (_b = {}, _b[key] = fileStats, _b)) });
}
case "progress": {
var key = action.key, progress = action.progress;
/* istanbul ignore next */
if (!state.stats[key]) {
throw new Error("Missing file with key \"".concat(key, "\""));
}
return __assign(__assign({}, state), { stats: __assign(__assign({}, state.stats), (_c = {}, _c[key] = __assign(__assign({}, state.stats[key]), { progress: progress }), _c)) });
}
case "complete": {
var key = action.key, result = action.result;
/* istanbul ignore next */
if (!state.stats[key]) {
throw new Error("Missing file with key \"".concat(key, "\""));
}
var file = {
key: key,
file: state.stats[key].file,
status: "complete",
result: result,
progress: 100,
};
var _e = state.readers, _f = key, _reader = _e[_f], readers_1 = __rest(_e, [typeof _f === "symbol" ? _f : _f + ""]);
return __assign(__assign({}, state), { readers: readers_1, stats: __assign(__assign({}, state.stats), (_d = {}, _d[key] = file, _d)) });
}
case "clearErrors":
return __assign(__assign({}, state), { errors: [] });
default:
/* istanbul ignore next */
return state;
}
}, {
stats: EMPTY_OBJECT,
errors: EMPTY_LIST,
readers: EMPTY_OBJECT,
}), 2), state = _m[0], dispatch = _m[1];
var stats = state.stats, errors = state.errors, readers = state.readers;
var statsList = Object.values(stats);
var totalFiles = statsList.length;
var totalBytes = statsList.reduce(function (result, _a) {
var size = _a.file.size;
return result + size;
}, 0);
var queueFiles = useCallback(function (files) {
var _a = validateFiles(files, {
maxFiles: maxFiles,
extensions: extensions,
minFileSize: minFileSize,
maxFileSize: maxFileSize,
totalBytes: totalBytes,
totalFiles: totalFiles,
totalFileSize: totalFileSize,
isValidFileName: isValidFileName,
}), pending = _a.pending, errors = _a.errors;
dispatch({ type: "queue", errors: errors, files: pending });
}, [
validateFiles,
maxFiles,
extensions,
minFileSize,
maxFileSize,
totalBytes,
totalFiles,
totalFileSize,
isValidFileName,
]);
var onDrop = useCallback(function (event) {
propOnDrop === null || propOnDrop === void 0 ? void 0 : propOnDrop(event);
event.preventDefault();
event.stopPropagation();
try {
var files = event.dataTransfer.files;
if (files) {
queueFiles(Array.from(files));
}
}
catch (e) {
dispatch({
type: "queue",
files: [],
errors: [
new FileAccessError(e instanceof Error ? e.message : undefined),
],
});
}
}, [queueFiles, propOnDrop]);
var onChange = useCallback(function (event) {
propOnChange === null || propOnChange === void 0 ? void 0 : propOnChange(event);
try {
var files = event.currentTarget.files;
if (files) {
queueFiles(Array.from(files));
}
else {
throw new Error();
}
}
catch (e) {
dispatch({
type: "queue",
files: [],
errors: [
new FileAccessError(e instanceof Error ? e.message : undefined),
],
});
}
}, [queueFiles, propOnChange]);
var remove = useCallback(function (keyOrKeys) {
var files = typeof keyOrKeys === "string" ? [keyOrKeys] : keyOrKeys;
files.forEach(function (fileKey) {
var _a;
(_a = readers[fileKey]) === null || _a === void 0 ? void 0 : _a.abort();
});
dispatch({ type: "remove", files: files });
}, [readers]);
var reset = useCallback(function () {
Object.values(readers).forEach(function (reader) {
reader.abort();
});
dispatch({ type: "reset" });
}, [readers]);
var clearErrors = useCallback(function () {
dispatch({ type: "clearErrors" });
}, []);
var start = useCallback(function (key, reader) {
dispatch({ type: "start", key: key, reader: reader });
}, []);
var complete = useCallback(function (key, result) {
if (result === void 0) { result = null; }
dispatch({ type: "complete", key: key, result: result });
}, []);
var createProgressEventHandler = useCallback(function (key) { return function (event) {
if (event.lengthComputable) {
var percentage = Math.round((event.loaded * 100) / event.total);
dispatch({ type: "progress", key: key, progress: percentage });
}
}; }, []);
useEffect(function () {
var pending = [];
var uploading = [];
Object.values(stats).forEach(function (file) {
if (file.status === "pending") {
pending.push(file);
}
else if (file.status === "uploading") {
uploading.push(file);
}
});
var lastIndex = concurrency === -1
? pending.length
: Math.max(0, concurrency - uploading.length);
var queue = pending.slice(0, lastIndex);
if (!queue.length) {
return;
}
queue.forEach(function (stats) {
var key = stats.key, file = stats.file;
var reader = new FileReader();
// using `addEventListener` instead of directly setting to
// `reader.progress`/`reader.load` so it's easier to test
reader.addEventListener("progress", createProgressEventHandler(key));
reader.addEventListener("load", function () {
complete(key, reader.result);
});
start(key, reader);
var parser = getFileParser(file);
/* istanbul ignore next */
if (process.env.NODE_ENV !== "production" &&
![
"readAsText",
"readAsDataURL",
"readAsArrayBuffer",
"readAsBinaryString",
].includes(parser)) {
throw new Error("Invalid file reader parser");
}
reader[parser](file);
});
}, [
concurrency,
stats,
getFileParser,
createProgressEventHandler,
start,
complete,
]);
var accept = "";
if (extensions.length) {
accept = extensions.reduce(function (s, ext) { return "".concat(s ? "".concat(s, ",") : "", ".").concat(ext); }, "");
}
return {
stats: statsList,
errors: errors,
accept: accept,
totalBytes: totalBytes,
totalFiles: totalFiles,
onDrop: onDrop,
onChange: onChange,
reset: reset,
remove: remove,
clearErrors: clearErrors,
};
}
//# sourceMappingURL=useFileUpload.js.map