I found a working solution (not necessarily the best?) eventually for my specific React project. I can drag and drop multiple glb and gltf files/folders/zip on my nettlify web now as you can see.
In the component, I created a custom hook to traverse through all the drag-and-drop files, including contents of zip, then get the files and put them into FilesInputStore.FilesToLoad
import { FilesInputStore } from "babylonjs";
import { loadAsync } from "jszip";
import { useDispatch } from "react-redux";
import { clearAllFiles, loadAllFiles } from "../reducer";
import { CompressedDirectoryEXT } from "../types";
import { getEXT, getPathAndName } from "../utils";
export function useLoadFiles() {
const dispatch = useDispatch();
return (itemList: DataTransferItemList) =>
queueFiles(itemList)
.then(() => dispatch(loadAllFiles()))
.catch(() => dispatch(clearAllFiles()));
}
function queueFiles(itemList: DataTransferItemList) {
const entries: FileSystemEntry[] = [];
for (let i = 0; i < itemList.length; i++) {
const entry = itemList[i].webkitGetAsEntry();
if (entry) entries.push(entry);
}
return Promise.all(entries.map((entry) => queueFileTree(entry)));
}
function queueFileTree(entry: FileSystemEntry, path = ""): Promise<void> {
return new Promise((resolve, reject) => {
const errorCallback = (error?: DOMException) =>
reject(`Failed to load ${entry.name}${error ? `: ${error}` : ""}`);
if (entry.isFile) {
(entry as FileSystemFileEntry).file((file) => {
if (getEXT(file.name) === CompressedDirectoryEXT.ZIP)
queueCompressedDirectory(file, path)
.then(() => resolve())
.catch(errorCallback);
else {
appendFileToLoad(path + file.name, file);
resolve();
}
}, errorCallback);
} else if (entry.isDirectory) {
const reader = (entry as FileSystemDirectoryEntry).createReader();
reader.readEntries(async (entries) => {
for (let i = 0; i < entries.length; i++)
await queueFileTree(entries[i], path + entry.name + "/");
resolve();
}, errorCallback);
} else errorCallback();
});
}
async function queueCompressedDirectory(zipFile: File, path: string) {
const unzipped = await loadAsync(zipFile);
const entries = Object.entries(unzipped.files);
for (let [entryPath, jsZipObject] of entries) {
if (jsZipObject.dir) continue;
const { name, path: pathWithinZip } = getPathAndName(entryPath);
const blob = await jsZipObject.async("blob");
const file = new File([blob], name);
const fullPath = path + zipFile.name + "/" + pathWithinZip;
if (getEXT(name) === CompressedDirectoryEXT.ZIP)
await queueCompressedDirectory(file, fullPath);
else appendFileToLoad(fullPath + name, file);
}
}
function appendFileToLoad(path: string, file: File) {
FilesInputStore.FilesToLoad[path.toLowerCase()] = file;
}
Then inside the middleware where I set up my scene, I go through all files inside FilesInputStore, look for anything with extension .glb and .gltf and load them
public async loadAllFiles() {
const pathNameCombined = Object.keys(FilesInputStore.FilesToLoad);
const loadedMeshes: AbstractMesh[] = [];
for (let i = pathNameCombined.length - 1; i >= 0; i--) {
const fullPath = pathNameCombined[i];
const ext = getEXT(fullPath);
if (!ext || !([Asset3DEXT.GLB, Asset3DEXT.GLTF] as EXT[]).includes(ext))
continue;
const { path, name } = getPathAndName(fullPath);
try {
const result = await SceneLoader.ImportMeshAsync(
"",
`file:${path}`,
name,
this.scene,
null,
ext
);
loadedMeshes.push(...this.processSceneLoaderResult(result, {}));
} catch {
throw new Error("Encountered error in loading " + fullPath);
}
}
return loadedMeshes;
}
My breakthrough was putting file:${path}
as url and making sure all keys inside FilesInputStore are lower case paths.
I am still not sure how to use FilesInput properly. It does seem to call reload callback when I drop file, but why reload callback is called but not success callback?
Source code for my project is GitHub - vtranduc/babylon-on-saga-experimental
I am still new to babylon after few months of coding in threejs and there’s so much questions I have about it. Thank you for responses!