use-file-picker
Version:
Simple react hook to open browser file selector.
359 lines (352 loc) • 12.7 kB
JavaScript
;
var react = require('react');
var fileSelector = require('file-selector');
function openFileDialog(accept, multiple, callback, initializeWithCustomAttributes) {
const inputElement = document.createElement("input");
inputElement.style.display = "none";
document.body.appendChild(inputElement);
inputElement.type = "file";
if (accept !== "*") inputElement.accept = accept;
inputElement.multiple = multiple;
inputElement.addEventListener("change", (arg) => {
callback(arg);
document.body.removeChild(inputElement);
});
inputElement.addEventListener("cancel", () => {
document.body.removeChild(inputElement);
});
if (initializeWithCustomAttributes) {
initializeWithCustomAttributes(inputElement);
}
inputElement.dispatchEvent(new MouseEvent("click"));
}
const useValidators = ({
onFilesSelected: onFilesSelectedProp,
onFilesSuccessfullySelected: onFilesSuccessfullySelectedProp,
onFilesRejected: onFilesRejectedProp,
onClear: onClearProp,
validators
}) => {
const onFilesSelected = react.useCallback(
(data) => {
onFilesSelectedProp == null ? void 0 : onFilesSelectedProp(data);
validators == null ? void 0 : validators.forEach((validator) => {
validator.onFilesSelected(data);
});
},
[onFilesSelectedProp, validators]
);
const onFilesSuccessfullySelected = react.useCallback(
(data) => {
onFilesSuccessfullySelectedProp == null ? void 0 : onFilesSuccessfullySelectedProp(data);
validators == null ? void 0 : validators.forEach((validator) => {
validator.onFilesSuccessfullySelected(data);
});
},
[validators, onFilesSuccessfullySelectedProp]
);
const onFilesRejected = react.useCallback(
(errors) => {
onFilesRejectedProp == null ? void 0 : onFilesRejectedProp(errors);
validators == null ? void 0 : validators.forEach((validator) => {
validator.onFilesRejected(errors);
});
},
[validators, onFilesRejectedProp]
);
const onClear = react.useCallback(() => {
onClearProp == null ? void 0 : onClearProp();
validators == null ? void 0 : validators.forEach((validator) => {
var _a;
(_a = validator.onClear) == null ? void 0 : _a.call(validator);
});
}, [validators, onClearProp]);
return {
onFilesSelected,
onFilesSuccessfullySelected,
onFilesRejected,
onClear
};
};
var __defProp$1 = Object.defineProperty;
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues$1 = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp$1.call(b, prop))
__defNormalProp$1(a, prop, b[prop]);
if (__getOwnPropSymbols$1)
for (var prop of __getOwnPropSymbols$1(b)) {
if (__propIsEnum$1.call(b, prop))
__defNormalProp$1(a, prop, b[prop]);
}
return a;
};
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp$1.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols$1)
for (var prop of __getOwnPropSymbols$1(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum$1.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
const EMPTY_ARRAY = [];
function useFilePicker(props = {}) {
const {
accept = "*",
multiple = true,
readAs = "Text",
readFilesContent = true,
validators = EMPTY_ARRAY,
initializeWithCustomParameters
} = props;
const [plainFiles, setPlainFiles] = react.useState([]);
const [filesContent, setFilesContent] = react.useState([]);
const [fileErrors, setFileErrors] = react.useState([]);
const [loading, setLoading] = react.useState(false);
const { onFilesSelected, onFilesSuccessfullySelected, onFilesRejected, onClear } = useValidators(props);
const clear = react.useCallback(() => {
setPlainFiles([]);
setFilesContent([]);
setFileErrors([]);
}, []);
const clearWithEventListener = react.useCallback(() => {
clear();
onClear == null ? void 0 : onClear();
}, [clear, onClear]);
const parseFile = react.useCallback(
(file) => new Promise(
(resolve, reject) => {
const reader = new FileReader();
const readStrategy = reader[`readAs${readAs}`];
readStrategy.call(reader, file, props.readAs === "Text" ? props.encoding : void 0);
const addError = (_a) => {
var others = __objRest(_a, []);
reject(__spreadValues$1({}, others));
};
reader.onload = () => __async(null, null, function* () {
return Promise.all(
validators.map(
(validator) => validator.validateAfterParsing(props, file, reader).catch((err) => Promise.reject(addError(err)))
)
).then(
() => resolve({
content: reader.result,
name: file.name,
lastModified: file.lastModified,
path: file.path,
size: file.size,
type: file.type
})
).catch(() => {
});
});
reader.onerror = () => {
addError({ name: "FileReaderError", readerError: reader.error, causedByFile: file });
};
}
),
[props, readAs, validators]
);
const openFilePicker = react.useCallback(() => {
const fileExtensions = accept instanceof Array ? accept.join(",") : accept;
openFileDialog(
fileExtensions,
multiple,
(evt) => __async(null, null, function* () {
clear();
setLoading(true);
const plainFileObjects = yield fileSelector.fromEvent(evt);
const validationsBeforeParsing = (yield Promise.all(
validators.map(
(validator) => validator.validateBeforeParsing(props, plainFileObjects).catch((err) => Array.isArray(err) ? err : [err])
)
)).flat(1).filter(Boolean);
setPlainFiles(plainFileObjects);
setFileErrors(validationsBeforeParsing);
if (validationsBeforeParsing.length) {
setPlainFiles([]);
onFilesRejected == null ? void 0 : onFilesRejected({ errors: validationsBeforeParsing });
onFilesSelected == null ? void 0 : onFilesSelected({ errors: validationsBeforeParsing });
setLoading(false);
return;
}
if (!readFilesContent) {
onFilesSelected == null ? void 0 : onFilesSelected({ plainFiles: plainFileObjects, filesContent: [] });
setLoading(false);
return;
}
const validationsAfterParsing = [];
const filesContent2 = yield Promise.all(
plainFileObjects.map(
(file) => parseFile(file).catch(
(fileError) => {
validationsAfterParsing.push(...Array.isArray(fileError) ? fileError : [fileError]);
}
)
)
);
setLoading(false);
if (validationsAfterParsing.length) {
setPlainFiles([]);
setFilesContent([]);
setFileErrors((errors) => [...errors, ...validationsAfterParsing]);
onFilesRejected == null ? void 0 : onFilesRejected({ errors: validationsAfterParsing });
onFilesSelected == null ? void 0 : onFilesSelected({
errors: validationsBeforeParsing.concat(validationsAfterParsing)
});
return;
}
setFilesContent(filesContent2);
setPlainFiles(plainFileObjects);
setFileErrors([]);
onFilesSuccessfullySelected == null ? void 0 : onFilesSuccessfullySelected({ filesContent: filesContent2, plainFiles: plainFileObjects });
onFilesSelected == null ? void 0 : onFilesSelected({
plainFiles: plainFileObjects,
filesContent: filesContent2
});
}),
initializeWithCustomParameters
);
}, [
props,
accept,
clear,
initializeWithCustomParameters,
multiple,
onFilesRejected,
onFilesSelected,
onFilesSuccessfullySelected,
parseFile,
readFilesContent,
validators
]);
return {
openFilePicker,
filesContent,
errors: fileErrors,
loading,
plainFiles,
clear: clearWithEventListener
};
}
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
function useImperativeFilePicker(props) {
const { onFilesSelected, onFilesSuccessfullySelected, validators, onFileRemoved } = props;
const [allPlainFiles, setAllPlainFiles] = react.useState([]);
const [allFilesContent, setAllFilesContent] = react.useState([]);
const { openFilePicker, loading, errors, clear } = useFilePicker(__spreadProps(__spreadValues({}, props), {
onFilesSelected: (data) => {
var _a;
if (!onFilesSelected) return;
if ((_a = data.errors) == null ? void 0 : _a.length) {
return onFilesSelected(data);
}
onFilesSelected({
errors: void 0,
plainFiles: [...allPlainFiles, ...data.plainFiles || []],
filesContent: [...allFilesContent, ...data.filesContent || []]
});
},
onFilesSuccessfullySelected: (data) => {
setAllPlainFiles((previousPlainFiles) => previousPlainFiles.concat(data.plainFiles));
setAllFilesContent((previousFilesContent) => previousFilesContent.concat(data.filesContent));
if (!onFilesSuccessfullySelected) return;
onFilesSuccessfullySelected({
plainFiles: [...allPlainFiles, ...data.plainFiles || []],
filesContent: [...allFilesContent, ...data.filesContent || []]
});
}
}));
const clearAll = react.useCallback(() => {
clear();
setAllPlainFiles([]);
setAllFilesContent([]);
}, [clear]);
const removeFileByIndex = react.useCallback(
(index) => {
setAllFilesContent((previousFilesContent) => [
...previousFilesContent.slice(0, index),
...previousFilesContent.slice(index + 1)
]);
setAllPlainFiles((previousPlainFiles) => {
const removedFile = previousPlainFiles[index];
if (!removedFile) {
return previousPlainFiles;
}
validators == null ? void 0 : validators.forEach((validator) => {
var _a;
return (_a = validator.onFileRemoved) == null ? void 0 : _a.call(validator, removedFile, index);
});
onFileRemoved == null ? void 0 : onFileRemoved(removedFile, index);
return [...previousPlainFiles.slice(0, index), ...previousPlainFiles.slice(index + 1)];
});
},
[validators, onFileRemoved]
);
const removeFileByReference = react.useCallback(
(file) => {
const index = allPlainFiles.findIndex((f) => f === file);
if (index === -1) return;
removeFileByIndex(index);
},
[removeFileByIndex, allPlainFiles]
);
return {
openFilePicker,
plainFiles: allPlainFiles,
filesContent: allFilesContent,
loading,
errors,
clear: clearAll,
removeFileByIndex,
removeFileByReference
};
}
exports.useFilePicker = useFilePicker;
exports.useImperativeFilePicker = useImperativeFilePicker;
//# sourceMappingURL=index.cjs.map