filesInput.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import { SceneLoader } from "../Loading/sceneLoader.js";
  2. import { Logger } from "../Misc/logger.js";
  3. import { FilesInputStore } from "./filesInputStore.js";
  4. /**
  5. * Class used to help managing file picking and drag-n-drop
  6. */
  7. export class FilesInput {
  8. /**
  9. * List of files ready to be loaded
  10. */
  11. static get FilesToLoad() {
  12. return FilesInputStore.FilesToLoad;
  13. }
  14. /**
  15. * Creates a new FilesInput
  16. * @param engine defines the rendering engine
  17. * @param scene defines the hosting scene
  18. * @param sceneLoadedCallback callback called when scene (files provided) is loaded
  19. * @param progressCallback callback called to track progress
  20. * @param additionalRenderLoopLogicCallback callback called to add user logic to the rendering loop
  21. * @param textureLoadingCallback callback called when a texture is loading
  22. * @param startingProcessingFilesCallback callback called when the system is about to process all files
  23. * @param onReloadCallback callback called when a reload is requested
  24. * @param errorCallback callback call if an error occurs
  25. * @param useAppend defines if the file loaded must be appended (true) or have the scene replaced (false, default behavior)
  26. */
  27. constructor(engine, scene, sceneLoadedCallback, progressCallback, additionalRenderLoopLogicCallback, textureLoadingCallback, startingProcessingFilesCallback, onReloadCallback, errorCallback, useAppend = false) {
  28. this.useAppend = useAppend;
  29. /**
  30. * Callback called when a file is processed
  31. * @returns false to abort the process
  32. */
  33. this.onProcessFileCallback = () => {
  34. return true;
  35. };
  36. /**
  37. * If a loading UI should be displayed while loading a file
  38. */
  39. this.displayLoadingUI = true;
  40. /**
  41. * Function used when loading the scene file
  42. * @param sceneFile defines the file to load
  43. * @param onProgress onProgress callback called while loading the file
  44. * @returns a promise completing when the load is complete
  45. */
  46. this.loadAsync = (sceneFile, onProgress) => this.useAppend ? SceneLoader.AppendAsync("file:", sceneFile, this._currentScene, onProgress) : SceneLoader.LoadAsync("file:", sceneFile, this._engine, onProgress);
  47. this._engine = engine;
  48. this._currentScene = scene;
  49. this._sceneLoadedCallback = sceneLoadedCallback;
  50. this._progressCallback = progressCallback;
  51. this._additionalRenderLoopLogicCallback = additionalRenderLoopLogicCallback;
  52. this._textureLoadingCallback = textureLoadingCallback;
  53. this._startingProcessingFilesCallback = startingProcessingFilesCallback;
  54. this._onReloadCallback = onReloadCallback;
  55. this._errorCallback = errorCallback;
  56. }
  57. /**
  58. * Calls this function to listen to drag'n'drop events on a specific DOM element
  59. * @param elementToMonitor defines the DOM element to track
  60. */
  61. monitorElementForDragNDrop(elementToMonitor) {
  62. if (elementToMonitor) {
  63. this._elementToMonitor = elementToMonitor;
  64. this._dragEnterHandler = (e) => {
  65. this._drag(e);
  66. };
  67. this._dragOverHandler = (e) => {
  68. this._drag(e);
  69. };
  70. this._dropHandler = (e) => {
  71. this._drop(e);
  72. };
  73. this._elementToMonitor.addEventListener("dragenter", this._dragEnterHandler, false);
  74. this._elementToMonitor.addEventListener("dragover", this._dragOverHandler, false);
  75. this._elementToMonitor.addEventListener("drop", this._dropHandler, false);
  76. }
  77. }
  78. /** Gets the current list of files to load */
  79. get filesToLoad() {
  80. return this._filesToLoad;
  81. }
  82. /**
  83. * Release all associated resources
  84. */
  85. dispose() {
  86. if (!this._elementToMonitor) {
  87. return;
  88. }
  89. this._elementToMonitor.removeEventListener("dragenter", this._dragEnterHandler);
  90. this._elementToMonitor.removeEventListener("dragover", this._dragOverHandler);
  91. this._elementToMonitor.removeEventListener("drop", this._dropHandler);
  92. }
  93. _renderFunction() {
  94. if (this._additionalRenderLoopLogicCallback) {
  95. this._additionalRenderLoopLogicCallback();
  96. }
  97. if (this._currentScene) {
  98. if (this._textureLoadingCallback) {
  99. const remaining = this._currentScene.getWaitingItemsCount();
  100. if (remaining > 0) {
  101. this._textureLoadingCallback(remaining);
  102. }
  103. }
  104. this._currentScene.render();
  105. }
  106. }
  107. _drag(e) {
  108. e.stopPropagation();
  109. e.preventDefault();
  110. }
  111. _drop(eventDrop) {
  112. eventDrop.stopPropagation();
  113. eventDrop.preventDefault();
  114. this.loadFiles(eventDrop);
  115. }
  116. _traverseFolder(folder, files, remaining, callback) {
  117. const reader = folder.createReader();
  118. const relativePath = folder.fullPath.replace(/^\//, "").replace(/(.+?)\/?$/, "$1/");
  119. reader.readEntries((entries) => {
  120. remaining.count += entries.length;
  121. for (const entry of entries) {
  122. if (entry.isFile) {
  123. entry.file((file) => {
  124. file.correctName = relativePath + file.name;
  125. files.push(file);
  126. if (--remaining.count === 0) {
  127. callback();
  128. }
  129. });
  130. }
  131. else if (entry.isDirectory) {
  132. this._traverseFolder(entry, files, remaining, callback);
  133. }
  134. }
  135. if (--remaining.count === 0) {
  136. callback();
  137. }
  138. });
  139. }
  140. _processFiles(files) {
  141. for (let i = 0; i < files.length; i++) {
  142. const name = files[i].correctName.toLowerCase();
  143. const extension = name.split(".").pop();
  144. if (!this.onProcessFileCallback(files[i], name, extension, (sceneFile) => (this._sceneFileToLoad = sceneFile))) {
  145. continue;
  146. }
  147. if (SceneLoader.IsPluginForExtensionAvailable("." + extension)) {
  148. this._sceneFileToLoad = files[i];
  149. }
  150. FilesInput.FilesToLoad[name] = files[i];
  151. }
  152. }
  153. /**
  154. * Load files from a drop event
  155. * @param event defines the drop event to use as source
  156. */
  157. loadFiles(event) {
  158. // Handling data transfer via drag'n'drop
  159. if (event && event.dataTransfer && event.dataTransfer.files) {
  160. this._filesToLoad = event.dataTransfer.files;
  161. }
  162. // Handling files from input files
  163. if (event && event.target && event.target.files) {
  164. this._filesToLoad = event.target.files;
  165. }
  166. if (!this._filesToLoad || this._filesToLoad.length === 0) {
  167. return;
  168. }
  169. if (this._startingProcessingFilesCallback) {
  170. this._startingProcessingFilesCallback(this._filesToLoad);
  171. }
  172. if (this._filesToLoad && this._filesToLoad.length > 0) {
  173. const files = [];
  174. const folders = [];
  175. const items = event.dataTransfer ? event.dataTransfer.items : null;
  176. for (let i = 0; i < this._filesToLoad.length; i++) {
  177. const fileToLoad = this._filesToLoad[i];
  178. const name = fileToLoad.name.toLowerCase();
  179. let entry;
  180. fileToLoad.correctName = name;
  181. if (items) {
  182. const item = items[i];
  183. if (item.getAsEntry) {
  184. entry = item.getAsEntry();
  185. }
  186. else if (item.webkitGetAsEntry) {
  187. entry = item.webkitGetAsEntry();
  188. }
  189. }
  190. if (!entry) {
  191. files.push(fileToLoad);
  192. }
  193. else {
  194. if (entry.isDirectory) {
  195. folders.push(entry);
  196. }
  197. else {
  198. files.push(fileToLoad);
  199. }
  200. }
  201. }
  202. if (folders.length === 0) {
  203. this._processFiles(files);
  204. this._processReload();
  205. }
  206. else {
  207. const remaining = { count: folders.length };
  208. for (const folder of folders) {
  209. this._traverseFolder(folder, files, remaining, () => {
  210. this._processFiles(files);
  211. if (remaining.count === 0) {
  212. this._processReload();
  213. }
  214. });
  215. }
  216. }
  217. }
  218. }
  219. _processReload() {
  220. if (this._onReloadCallback) {
  221. this._onReloadCallback(this._sceneFileToLoad);
  222. }
  223. else {
  224. this.reload();
  225. }
  226. }
  227. /**
  228. * Reload the current scene from the loaded files
  229. */
  230. reload() {
  231. // If a scene file has been provided
  232. if (this._sceneFileToLoad) {
  233. if (!this.useAppend) {
  234. if (this._currentScene) {
  235. if (Logger.errorsCount > 0) {
  236. Logger.ClearLogCache();
  237. }
  238. this._engine.stopRenderLoop();
  239. }
  240. }
  241. SceneLoader.ShowLoadingScreen = false;
  242. if (this.displayLoadingUI) {
  243. this._engine.displayLoadingUI();
  244. }
  245. this.loadAsync(this._sceneFileToLoad, this._progressCallback)
  246. .then((scene) => {
  247. // if appending do nothing
  248. if (!this.useAppend) {
  249. if (this._currentScene) {
  250. this._currentScene.dispose();
  251. }
  252. this._currentScene = scene;
  253. // Wait for textures and shaders to be ready
  254. this._currentScene.executeWhenReady(() => {
  255. if (this.displayLoadingUI) {
  256. this._engine.hideLoadingUI();
  257. }
  258. this._engine.runRenderLoop(() => {
  259. this._renderFunction();
  260. });
  261. });
  262. }
  263. else {
  264. if (this.displayLoadingUI) {
  265. this._engine.hideLoadingUI();
  266. }
  267. }
  268. if (this._sceneLoadedCallback && this._currentScene) {
  269. this._sceneLoadedCallback(this._sceneFileToLoad, this._currentScene);
  270. }
  271. })
  272. .catch((error) => {
  273. if (this.displayLoadingUI) {
  274. this._engine.hideLoadingUI();
  275. }
  276. if (this._errorCallback) {
  277. this._errorCallback(this._sceneFileToLoad, this._currentScene, error.message);
  278. }
  279. });
  280. }
  281. else {
  282. Logger.Error("Please provide a valid .babylon file.");
  283. }
  284. }
  285. }
  286. //# sourceMappingURL=filesInput.js.map