@iobroker/gui-components 8.0.1

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.
Files changed (368) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +1450 -0
  3. package/build/AdminConnection.d.ts +1 -0
  4. package/build/AdminConnection.js +2 -0
  5. package/build/AdminConnection.js.map +1 -0
  6. package/build/Components/404.d.ts +4 -0
  7. package/build/Components/404.js +97 -0
  8. package/build/Components/404.js.map +1 -0
  9. package/build/Components/ColorPicker.d.ts +86 -0
  10. package/build/Components/ColorPicker.js +222 -0
  11. package/build/Components/ColorPicker.js.map +1 -0
  12. package/build/Components/ComplexCron.d.ts +40 -0
  13. package/build/Components/ComplexCron.js +401 -0
  14. package/build/Components/ComplexCron.js.map +1 -0
  15. package/build/Components/CopyToClipboard.d.ts +14 -0
  16. package/build/Components/CopyToClipboard.js +160 -0
  17. package/build/Components/CopyToClipboard.js.map +1 -0
  18. package/build/Components/CustomModal.d.ts +26 -0
  19. package/build/Components/CustomModal.js +62 -0
  20. package/build/Components/CustomModal.js.map +1 -0
  21. package/build/Components/DeviceType/DeviceTypeIcon.d.ts +29 -0
  22. package/build/Components/DeviceType/DeviceTypeIcon.js +108 -0
  23. package/build/Components/DeviceType/DeviceTypeIcon.js.map +1 -0
  24. package/build/Components/DeviceType/DeviceTypeOptions.d.ts +2 -0
  25. package/build/Components/DeviceType/DeviceTypeOptions.js +69 -0
  26. package/build/Components/DeviceType/DeviceTypeOptions.js.map +1 -0
  27. package/build/Components/DeviceType/DeviceTypeSelector.d.ts +20 -0
  28. package/build/Components/DeviceType/DeviceTypeSelector.js +83 -0
  29. package/build/Components/DeviceType/DeviceTypeSelector.js.map +1 -0
  30. package/build/Components/DeviceType/deviceTypeTranslations.d.ts +1 -0
  31. package/build/Components/DeviceType/deviceTypeTranslations.js +30 -0
  32. package/build/Components/DeviceType/deviceTypeTranslations.js.map +1 -0
  33. package/build/Components/DeviceType/i18n/de.json +46 -0
  34. package/build/Components/DeviceType/i18n/en.json +46 -0
  35. package/build/Components/DeviceType/i18n/fr.json +46 -0
  36. package/build/Components/DeviceType/i18n/it.json +46 -0
  37. package/build/Components/DeviceType/i18n/nl.json +46 -0
  38. package/build/Components/DeviceType/i18n/pl.json +46 -0
  39. package/build/Components/DeviceType/i18n/pt.json +46 -0
  40. package/build/Components/DeviceType/i18n/ru.json +46 -0
  41. package/build/Components/DeviceType/i18n/uk.json +46 -0
  42. package/build/Components/DeviceType/i18n/zh-cn.json +46 -0
  43. package/build/Components/DeviceType/icons/Cleaner.d.ts +3 -0
  44. package/build/Components/DeviceType/icons/Cleaner.js +14 -0
  45. package/build/Components/DeviceType/icons/Cleaner.js.map +1 -0
  46. package/build/Components/DeviceType/icons/DoorClosed.d.ts +3 -0
  47. package/build/Components/DeviceType/icons/DoorClosed.js +7 -0
  48. package/build/Components/DeviceType/icons/DoorClosed.js.map +1 -0
  49. package/build/Components/DeviceType/icons/DoorOpened.d.ts +3 -0
  50. package/build/Components/DeviceType/icons/DoorOpened.js +7 -0
  51. package/build/Components/DeviceType/icons/DoorOpened.js.map +1 -0
  52. package/build/Components/DeviceType/icons/FireOff.d.ts +3 -0
  53. package/build/Components/DeviceType/icons/FireOff.js +8 -0
  54. package/build/Components/DeviceType/icons/FireOff.js.map +1 -0
  55. package/build/Components/DeviceType/icons/FireOn.d.ts +3 -0
  56. package/build/Components/DeviceType/icons/FireOn.js +14 -0
  57. package/build/Components/DeviceType/icons/FireOn.js.map +1 -0
  58. package/build/Components/DeviceType/icons/FloodOff.d.ts +3 -0
  59. package/build/Components/DeviceType/icons/FloodOff.js +11 -0
  60. package/build/Components/DeviceType/icons/FloodOff.js.map +1 -0
  61. package/build/Components/DeviceType/icons/FloodOn.d.ts +3 -0
  62. package/build/Components/DeviceType/icons/FloodOn.js +18 -0
  63. package/build/Components/DeviceType/icons/FloodOn.js.map +1 -0
  64. package/build/Components/DeviceType/icons/Gate.d.ts +3 -0
  65. package/build/Components/DeviceType/icons/Gate.js +8 -0
  66. package/build/Components/DeviceType/icons/Gate.js.map +1 -0
  67. package/build/Components/DeviceType/icons/HeatValve.d.ts +3 -0
  68. package/build/Components/DeviceType/icons/HeatValve.js +6 -0
  69. package/build/Components/DeviceType/icons/HeatValve.js.map +1 -0
  70. package/build/Components/DeviceType/icons/Home.d.ts +3 -0
  71. package/build/Components/DeviceType/icons/Home.js +6 -0
  72. package/build/Components/DeviceType/icons/Home.js.map +1 -0
  73. package/build/Components/DeviceType/icons/Humidity.d.ts +3 -0
  74. package/build/Components/DeviceType/icons/Humidity.js +8 -0
  75. package/build/Components/DeviceType/icons/Humidity.js.map +1 -0
  76. package/build/Components/DeviceType/icons/IconHome.d.ts +5 -0
  77. package/build/Components/DeviceType/icons/IconHome.js +21 -0
  78. package/build/Components/DeviceType/icons/IconHome.js.map +1 -0
  79. package/build/Components/DeviceType/icons/Jalousie.d.ts +3 -0
  80. package/build/Components/DeviceType/icons/Jalousie.js +7 -0
  81. package/build/Components/DeviceType/icons/Jalousie.js.map +1 -0
  82. package/build/Components/DeviceType/icons/Material.d.ts +3 -0
  83. package/build/Components/DeviceType/icons/Material.js +8 -0
  84. package/build/Components/DeviceType/icons/Material.js.map +1 -0
  85. package/build/Components/DeviceType/icons/MotionOff.d.ts +3 -0
  86. package/build/Components/DeviceType/icons/MotionOff.js +11 -0
  87. package/build/Components/DeviceType/icons/MotionOff.js.map +1 -0
  88. package/build/Components/DeviceType/icons/MotionOn.d.ts +3 -0
  89. package/build/Components/DeviceType/icons/MotionOn.js +7 -0
  90. package/build/Components/DeviceType/icons/MotionOn.js.map +1 -0
  91. package/build/Components/DeviceType/icons/PushButton.d.ts +3 -0
  92. package/build/Components/DeviceType/icons/PushButton.js +9 -0
  93. package/build/Components/DeviceType/icons/PushButton.js.map +1 -0
  94. package/build/Components/DeviceType/icons/RGB.d.ts +3 -0
  95. package/build/Components/DeviceType/icons/RGB.js +43 -0
  96. package/build/Components/DeviceType/icons/RGB.js.map +1 -0
  97. package/build/Components/DeviceType/icons/RepairExpert.d.ts +3 -0
  98. package/build/Components/DeviceType/icons/RepairExpert.js +6 -0
  99. package/build/Components/DeviceType/icons/RepairExpert.js.map +1 -0
  100. package/build/Components/DeviceType/icons/Socket.d.ts +3 -0
  101. package/build/Components/DeviceType/icons/Socket.js +8 -0
  102. package/build/Components/DeviceType/icons/Socket.js.map +1 -0
  103. package/build/Components/DeviceType/icons/Thermometer.d.ts +3 -0
  104. package/build/Components/DeviceType/icons/Thermometer.js +7 -0
  105. package/build/Components/DeviceType/icons/Thermometer.js.map +1 -0
  106. package/build/Components/DeviceType/icons/ThermometerSimple.d.ts +3 -0
  107. package/build/Components/DeviceType/icons/ThermometerSimple.js +8 -0
  108. package/build/Components/DeviceType/icons/ThermometerSimple.js.map +1 -0
  109. package/build/Components/DeviceType/icons/Thermostat.d.ts +3 -0
  110. package/build/Components/DeviceType/icons/Thermostat.js +9 -0
  111. package/build/Components/DeviceType/icons/Thermostat.js.map +1 -0
  112. package/build/Components/DeviceType/icons/Valve.d.ts +3 -0
  113. package/build/Components/DeviceType/icons/Valve.js +7 -0
  114. package/build/Components/DeviceType/icons/Valve.js.map +1 -0
  115. package/build/Components/DeviceType/icons/WindowClosed.d.ts +3 -0
  116. package/build/Components/DeviceType/icons/WindowClosed.js +8 -0
  117. package/build/Components/DeviceType/icons/WindowClosed.js.map +1 -0
  118. package/build/Components/DeviceType/icons/WindowOpened.d.ts +3 -0
  119. package/build/Components/DeviceType/icons/WindowOpened.js +9 -0
  120. package/build/Components/DeviceType/icons/WindowOpened.js.map +1 -0
  121. package/build/Components/DeviceType/icons/WindowTilted.d.ts +3 -0
  122. package/build/Components/DeviceType/icons/WindowTilted.js +8 -0
  123. package/build/Components/DeviceType/icons/WindowTilted.js.map +1 -0
  124. package/build/Components/DeviceType/icons/index.d.ts +28 -0
  125. package/build/Components/DeviceType/icons/index.js +28 -0
  126. package/build/Components/DeviceType/icons/index.js.map +1 -0
  127. package/build/Components/FileBrowser.d.ts +187 -0
  128. package/build/Components/FileBrowser.js +1711 -0
  129. package/build/Components/FileBrowser.js.map +1 -0
  130. package/build/Components/FileViewer.d.ts +49 -0
  131. package/build/Components/FileViewer.js +247 -0
  132. package/build/Components/FileViewer.js.map +1 -0
  133. package/build/Components/Icon.d.ts +35 -0
  134. package/build/Components/Icon.js +140 -0
  135. package/build/Components/Icon.js.map +1 -0
  136. package/build/Components/IconPicker.d.ts +25 -0
  137. package/build/Components/IconPicker.js +72 -0
  138. package/build/Components/IconPicker.js.map +1 -0
  139. package/build/Components/IconSelector.d.ts +33 -0
  140. package/build/Components/IconSelector.js +2086 -0
  141. package/build/Components/IconSelector.js.map +1 -0
  142. package/build/Components/Image.d.ts +31 -0
  143. package/build/Components/Image.js +126 -0
  144. package/build/Components/Image.js.map +1 -0
  145. package/build/Components/InfoBox.d.ts +37 -0
  146. package/build/Components/InfoBox.js +115 -0
  147. package/build/Components/InfoBox.js.map +1 -0
  148. package/build/Components/IobUri.d.ts +14 -0
  149. package/build/Components/IobUri.js +168 -0
  150. package/build/Components/IobUri.js.map +1 -0
  151. package/build/Components/Loader.d.ts +31 -0
  152. package/build/Components/Loader.js +265 -0
  153. package/build/Components/Loader.js.map +1 -0
  154. package/build/Components/Loaders/MV.d.ts +17 -0
  155. package/build/Components/Loaders/MV.js +61 -0
  156. package/build/Components/Loaders/MV.js.map +1 -0
  157. package/build/Components/Loaders/PT.d.ts +27 -0
  158. package/build/Components/Loaders/PT.js +150 -0
  159. package/build/Components/Loaders/PT.js.map +1 -0
  160. package/build/Components/Loaders/Vendor.d.ts +22 -0
  161. package/build/Components/Loaders/Vendor.js +52 -0
  162. package/build/Components/Loaders/Vendor.js.map +1 -0
  163. package/build/Components/Logo.d.ts +18 -0
  164. package/build/Components/Logo.js +109 -0
  165. package/build/Components/Logo.js.map +1 -0
  166. package/build/Components/MDUtils.d.ts +11 -0
  167. package/build/Components/MDUtils.js +99 -0
  168. package/build/Components/MDUtils.js.map +1 -0
  169. package/build/Components/ObjectBrowser.d.ts +665 -0
  170. package/build/Components/ObjectBrowser.js +6245 -0
  171. package/build/Components/ObjectBrowser.js.map +1 -0
  172. package/build/Components/Router.d.ts +21 -0
  173. package/build/Components/Router.js +77 -0
  174. package/build/Components/Router.js.map +1 -0
  175. package/build/Components/SaveCloseButtons.d.ts +20 -0
  176. package/build/Components/SaveCloseButtons.js +65 -0
  177. package/build/Components/SaveCloseButtons.js.map +1 -0
  178. package/build/Components/Schedule.d.ts +62 -0
  179. package/build/Components/Schedule.js +1266 -0
  180. package/build/Components/Schedule.js.map +1 -0
  181. package/build/Components/SelectWithIcon.d.ts +37 -0
  182. package/build/Components/SelectWithIcon.js +139 -0
  183. package/build/Components/SelectWithIcon.js.map +1 -0
  184. package/build/Components/SimpleCron/cronText.d.ts +10 -0
  185. package/build/Components/SimpleCron/cronText.js +14 -0
  186. package/build/Components/SimpleCron/cronText.js.map +1 -0
  187. package/build/Components/SimpleCron/index.d.ts +55 -0
  188. package/build/Components/SimpleCron/index.js +698 -0
  189. package/build/Components/SimpleCron/index.js.map +1 -0
  190. package/build/Components/TabContainer.d.ts +15 -0
  191. package/build/Components/TabContainer.js +23 -0
  192. package/build/Components/TabContainer.js.map +1 -0
  193. package/build/Components/TabContent.d.ts +11 -0
  194. package/build/Components/TabContent.js +20 -0
  195. package/build/Components/TabContent.js.map +1 -0
  196. package/build/Components/TabHeader.d.ts +6 -0
  197. package/build/Components/TabHeader.js +6 -0
  198. package/build/Components/TabHeader.js.map +1 -0
  199. package/build/Components/TableResize.d.ts +45 -0
  200. package/build/Components/TableResize.js +204 -0
  201. package/build/Components/TableResize.js.map +1 -0
  202. package/build/Components/TextWithIcon.d.ts +21 -0
  203. package/build/Components/TextWithIcon.js +113 -0
  204. package/build/Components/TextWithIcon.js.map +1 -0
  205. package/build/Components/ToggleThemeMenu.d.ts +12 -0
  206. package/build/Components/ToggleThemeMenu.js +13 -0
  207. package/build/Components/ToggleThemeMenu.js.map +1 -0
  208. package/build/Components/TreeTable.d.ts +79 -0
  209. package/build/Components/TreeTable.js +538 -0
  210. package/build/Components/TreeTable.js.map +1 -0
  211. package/build/Components/UploadImage.d.ts +23 -0
  212. package/build/Components/UploadImage.js +516 -0
  213. package/build/Components/UploadImage.js.map +1 -0
  214. package/build/Components/Utils.d.ts +331 -0
  215. package/build/Components/Utils.js +1605 -0
  216. package/build/Components/Utils.js.map +1 -0
  217. package/build/Components/withWidth.d.ts +2 -0
  218. package/build/Components/withWidth.js +22 -0
  219. package/build/Components/withWidth.js.map +1 -0
  220. package/build/Connection.d.ts +1 -0
  221. package/build/Connection.js +2 -0
  222. package/build/Connection.js.map +1 -0
  223. package/build/Dialogs/ComplexCron.d.ts +24 -0
  224. package/build/Dialogs/ComplexCron.js +85 -0
  225. package/build/Dialogs/ComplexCron.js.map +1 -0
  226. package/build/Dialogs/Confirm.d.ts +39 -0
  227. package/build/Dialogs/Confirm.js +83 -0
  228. package/build/Dialogs/Confirm.js.map +1 -0
  229. package/build/Dialogs/Cron.d.ts +29 -0
  230. package/build/Dialogs/Cron.js +72 -0
  231. package/build/Dialogs/Cron.js.map +1 -0
  232. package/build/Dialogs/Error.d.ts +18 -0
  233. package/build/Dialogs/Error.js +27 -0
  234. package/build/Dialogs/Error.js.map +1 -0
  235. package/build/Dialogs/Message.d.ts +20 -0
  236. package/build/Dialogs/Message.js +29 -0
  237. package/build/Dialogs/Message.js.map +1 -0
  238. package/build/Dialogs/SelectFile.d.ts +85 -0
  239. package/build/Dialogs/SelectFile.js +115 -0
  240. package/build/Dialogs/SelectFile.js.map +1 -0
  241. package/build/Dialogs/SelectID.d.ts +102 -0
  242. package/build/Dialogs/SelectID.js +151 -0
  243. package/build/Dialogs/SelectID.js.map +1 -0
  244. package/build/Dialogs/SimpleCron.d.ts +19 -0
  245. package/build/Dialogs/SimpleCron.js +46 -0
  246. package/build/Dialogs/SimpleCron.js.map +1 -0
  247. package/build/Dialogs/TextInput.d.ts +2 -0
  248. package/build/Dialogs/TextInput.js +31 -0
  249. package/build/Dialogs/TextInput.js.map +1 -0
  250. package/build/GenericApp.d.ts +209 -0
  251. package/build/GenericApp.js +842 -0
  252. package/build/GenericApp.js.map +1 -0
  253. package/build/LegacyConnection.d.ts +938 -0
  254. package/build/LegacyConnection.js +2849 -0
  255. package/build/LegacyConnection.js.map +1 -0
  256. package/build/Prompt.d.ts +5 -0
  257. package/build/Prompt.js +19 -0
  258. package/build/Prompt.js.map +1 -0
  259. package/build/Theme.d.ts +5 -0
  260. package/build/Theme.js +344 -0
  261. package/build/Theme.js.map +1 -0
  262. package/build/assets/devices/parseNames.d.ts +0 -0
  263. package/build/assets/devices/parseNames.js +35 -0
  264. package/build/assets/devices/parseNames.js.map +1 -0
  265. package/build/assets/devices.json +1 -0
  266. package/build/assets/lamp_ceiling.svg +9 -0
  267. package/build/assets/lamp_table.svg +8 -0
  268. package/build/assets/no_icon.svg +9 -0
  269. package/build/assets/rooms/parseNames.d.ts +0 -0
  270. package/build/assets/rooms/parseNames.js +35 -0
  271. package/build/assets/rooms/parseNames.js.map +1 -0
  272. package/build/assets/rooms.json +1 -0
  273. package/build/dictionary.d.ts +1 -0
  274. package/build/dictionary.js +25 -0
  275. package/build/dictionary.js.map +1 -0
  276. package/build/globals.d.ts +19 -0
  277. package/build/i18n/de.json +480 -0
  278. package/build/i18n/en.json +480 -0
  279. package/build/i18n/es.json +480 -0
  280. package/build/i18n/fr.json +480 -0
  281. package/build/i18n/it.json +475 -0
  282. package/build/i18n/nl.json +475 -0
  283. package/build/i18n/pl.json +475 -0
  284. package/build/i18n/pt.json +475 -0
  285. package/build/i18n/ru.json +475 -0
  286. package/build/i18n/uk.json +475 -0
  287. package/build/i18n/zh-cn.json +475 -0
  288. package/build/i18n.d.ts +84 -0
  289. package/build/i18n.js +224 -0
  290. package/build/i18n.js.map +1 -0
  291. package/build/icons/IconAdapter.d.ts +3 -0
  292. package/build/icons/IconAdapter.js +7 -0
  293. package/build/icons/IconAdapter.js.map +1 -0
  294. package/build/icons/IconAlias.d.ts +3 -0
  295. package/build/icons/IconAlias.js +7 -0
  296. package/build/icons/IconAlias.js.map +1 -0
  297. package/build/icons/IconButtonImage.d.ts +3 -0
  298. package/build/icons/IconButtonImage.js +9 -0
  299. package/build/icons/IconButtonImage.js.map +1 -0
  300. package/build/icons/IconChannel.d.ts +3 -0
  301. package/build/icons/IconChannel.js +10 -0
  302. package/build/icons/IconChannel.js.map +1 -0
  303. package/build/icons/IconClearFilter.d.ts +3 -0
  304. package/build/icons/IconClearFilter.js +8 -0
  305. package/build/icons/IconClearFilter.js.map +1 -0
  306. package/build/icons/IconClosed.d.ts +3 -0
  307. package/build/icons/IconClosed.js +7 -0
  308. package/build/icons/IconClosed.js.map +1 -0
  309. package/build/icons/IconCopy.d.ts +3 -0
  310. package/build/icons/IconCopy.js +6 -0
  311. package/build/icons/IconCopy.js.map +1 -0
  312. package/build/icons/IconDevice.d.ts +3 -0
  313. package/build/icons/IconDevice.js +16 -0
  314. package/build/icons/IconDevice.js.map +1 -0
  315. package/build/icons/IconDocument.d.ts +3 -0
  316. package/build/icons/IconDocument.js +7 -0
  317. package/build/icons/IconDocument.js.map +1 -0
  318. package/build/icons/IconDocumentReadOnly.d.ts +3 -0
  319. package/build/icons/IconDocumentReadOnly.js +8 -0
  320. package/build/icons/IconDocumentReadOnly.js.map +1 -0
  321. package/build/icons/IconExpert.d.ts +3 -0
  322. package/build/icons/IconExpert.js +7 -0
  323. package/build/icons/IconExpert.js.map +1 -0
  324. package/build/icons/IconFx.d.ts +3 -0
  325. package/build/icons/IconFx.js +6 -0
  326. package/build/icons/IconFx.js.map +1 -0
  327. package/build/icons/IconInstance.d.ts +3 -0
  328. package/build/icons/IconInstance.js +7 -0
  329. package/build/icons/IconInstance.js.map +1 -0
  330. package/build/icons/IconLogout.d.ts +3 -0
  331. package/build/icons/IconLogout.js +7 -0
  332. package/build/icons/IconLogout.js.map +1 -0
  333. package/build/icons/IconNoIcon.d.ts +3 -0
  334. package/build/icons/IconNoIcon.js +6 -0
  335. package/build/icons/IconNoIcon.js.map +1 -0
  336. package/build/icons/IconOpen.d.ts +3 -0
  337. package/build/icons/IconOpen.js +7 -0
  338. package/build/icons/IconOpen.js.map +1 -0
  339. package/build/icons/IconState.d.ts +3 -0
  340. package/build/icons/IconState.js +7 -0
  341. package/build/icons/IconState.js.map +1 -0
  342. package/build/icons/IconVacuum.d.ts +3 -0
  343. package/build/icons/IconVacuum.js +12 -0
  344. package/build/icons/IconVacuum.js.map +1 -0
  345. package/build/index.css +56 -0
  346. package/build/index.d.ts +87 -0
  347. package/build/index.js +85 -0
  348. package/build/index.js.map +1 -0
  349. package/build/modulefederation.admin.config.d.ts +13 -0
  350. package/build/modulefederation.admin.config.js +45 -0
  351. package/build/modulefederation.admin.config.js.map +1 -0
  352. package/build/types.d.ts +236 -0
  353. package/i18n/de.json +480 -0
  354. package/i18n/en.json +480 -0
  355. package/i18n/es.json +480 -0
  356. package/i18n/fr.json +480 -0
  357. package/i18n/it.json +475 -0
  358. package/i18n/nl.json +475 -0
  359. package/i18n/pl.json +475 -0
  360. package/i18n/pt.json +475 -0
  361. package/i18n/ru.json +475 -0
  362. package/i18n/uk.json +475 -0
  363. package/i18n/zh-cn.json +475 -0
  364. package/index.css +56 -0
  365. package/modulefederation.admin.config.d.ts +20 -0
  366. package/modulefederation.admin.config.js +54 -0
  367. package/package.json +86 -0
  368. package/tasksExample.js +91 -0
@@ -0,0 +1,1711 @@
1
+ /**
2
+ * Copyright 2020-2025, Denis Haev <dogafox@gmail.com>
3
+ *
4
+ * MIT License
5
+ *
6
+ */
7
+ import React, { Component } from 'react';
8
+ import Dropzone from 'react-dropzone';
9
+ import { LinearProgress, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip, CircularProgress, Toolbar, IconButton, Fab, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button, Input, Breadcrumbs, Box, Checkbox, FormControlLabel, } from '@mui/material';
10
+ // MUI Icons
11
+ import { ArrowBack as IconBack, AudioFile as TypeIconAudio, Bookmark as JsonIcon, BookmarkBorder as CssIcon, Brightness6 as Brightness5Icon, Close as CloseIcon, Code as JSIcon, CreateNewFolder as AddFolderIcon, Delete as DeleteIcon, Description as HtmlIcon, Edit as EditIcon, FolderOpen as EmptyFilterIcon, FolderSpecial as RestrictedIcon, FontDownload as TypeIconTxt, Image as TypeIconImages, InsertDriveFile as FileIcon, KeyboardReturn as EnterIcon, List as IconList, MusicNote as MusicIcon, Publish as UploadIcon, Refresh as RefreshIcon, SaveAlt as DownloadIcon, Videocam as TypeIconVideo, ViewModule as IconTile, } from '@mui/icons-material';
12
+ import { DialogError } from '../Dialogs/Error';
13
+ import { Utils } from './Utils';
14
+ import { DialogTextInput } from '../Dialogs/TextInput';
15
+ // Custom Icons
16
+ import { IconExpert } from '../icons/IconExpert';
17
+ import { IconClosed } from '../icons/IconClosed';
18
+ import { IconOpen } from '../icons/IconOpen';
19
+ import { IconNoIcon } from '../icons/IconNoIcon';
20
+ import { Icon } from './Icon';
21
+ import { withWidth } from './withWidth';
22
+ import { FileViewer, EXTENSIONS } from './FileViewer';
23
+ const ROW_HEIGHT = 32;
24
+ const BUTTON_WIDTH = 32;
25
+ const TILE_HEIGHT = 120;
26
+ const TILE_WIDTH = 64;
27
+ const NOT_FOUND = 'Not found';
28
+ const FILE_TYPE_ICONS = {
29
+ all: FileIcon,
30
+ images: TypeIconImages,
31
+ code: JSIcon,
32
+ txt: TypeIconTxt,
33
+ audio: TypeIconAudio,
34
+ video: TypeIconVideo,
35
+ };
36
+ const styles = {
37
+ root: {
38
+ width: '100%',
39
+ overflow: 'hidden',
40
+ height: '100%',
41
+ position: 'relative',
42
+ },
43
+ filesDiv: {
44
+ width: 'calc(100% - 8px)',
45
+ overflowX: 'hidden',
46
+ overflowY: 'auto',
47
+ padding: 8,
48
+ },
49
+ filesDivHint: {
50
+ position: 'absolute',
51
+ bottom: 0,
52
+ left: 20,
53
+ opacity: 0.7,
54
+ fontStyle: 'italic',
55
+ fontSize: 12,
56
+ },
57
+ filesDivTable: {
58
+ height: 'calc(100% - 56px)',
59
+ },
60
+ filesDivTile: {
61
+ height: `calc(100% - ${48 * 2 + 8}px)`,
62
+ display: 'flex',
63
+ alignContent: 'flex-start',
64
+ alignItems: 'stretch',
65
+ flexWrap: 'wrap',
66
+ flex: `0 0 ${TILE_WIDTH}px`,
67
+ },
68
+ itemTile: (theme) => ({
69
+ position: 'relative',
70
+ userSelect: 'none',
71
+ cursor: 'pointer',
72
+ height: TILE_HEIGHT,
73
+ width: TILE_WIDTH,
74
+ display: 'inline-block',
75
+ textAlign: 'center',
76
+ opacity: 0.1,
77
+ transition: 'opacity 1s',
78
+ margin: '4px',
79
+ borderRadius: '4px',
80
+ '&:hover': {
81
+ background: theme.palette.secondary.light,
82
+ color: Utils.invertColor(theme.palette.secondary.main, true),
83
+ },
84
+ }),
85
+ itemNameFolderTile: {
86
+ fontWeight: 'bold',
87
+ },
88
+ itemNameTile: {
89
+ width: '100%',
90
+ height: 32,
91
+ overflow: 'hidden',
92
+ textOverflow: 'ellipsis',
93
+ fontSize: 12,
94
+ textAlign: 'center',
95
+ wordBreak: 'break-all',
96
+ },
97
+ itemFolderIconTile: (theme) => ({
98
+ width: '100%',
99
+ height: TILE_HEIGHT - 32 - 16 - 8, // name + size
100
+ display: 'block',
101
+ pl: 1,
102
+ color: theme.palette.secondary.main || '#fbff7d',
103
+ }),
104
+ itemFolderIconBack: (theme) => ({
105
+ position: 'absolute',
106
+ top: 22,
107
+ left: 18,
108
+ zIndex: 1,
109
+ color: theme.palette.mode === 'dark' ? '#FFF' : '#FFF',
110
+ }),
111
+ itemSizeTile: {
112
+ width: '100%',
113
+ height: 16,
114
+ textAlign: 'center',
115
+ fontSize: 10,
116
+ },
117
+ itemImageTile: {
118
+ width: 'calc(100% - 8px)',
119
+ height: TILE_HEIGHT - 32 - 16 - 8, // name + size
120
+ margin: 4,
121
+ display: 'block',
122
+ textAlign: 'center',
123
+ objectFit: 'contain',
124
+ },
125
+ itemIconTile: {
126
+ width: '100%',
127
+ height: TILE_HEIGHT - 32 - 16 - 8, // name + size
128
+ display: 'block',
129
+ objectFit: 'contain',
130
+ },
131
+ itemSelected: (theme) => ({
132
+ background: theme.palette.primary.main,
133
+ color: Utils.invertColor(theme.palette.primary.main, true),
134
+ }),
135
+ itemTable: (theme) => ({
136
+ userSelect: 'none',
137
+ cursor: 'pointer',
138
+ height: ROW_HEIGHT,
139
+ display: 'inline-flex',
140
+ lineHeight: `${ROW_HEIGHT}px`,
141
+ '&:hover': {
142
+ background: theme.palette.secondary.light,
143
+ color: Utils.invertColor(theme.palette.secondary.main, true),
144
+ },
145
+ }),
146
+ itemNameTable: {
147
+ display: 'inline-block',
148
+ pl: '10px',
149
+ fontSize: '1rem',
150
+ verticalAlign: 'top',
151
+ flexGrow: 1,
152
+ textOverflow: 'ellipsis',
153
+ whiteSpace: 'nowrap',
154
+ overflow: 'hidden',
155
+ '@media screen and (max-width: 500px)': {
156
+ textAlign: 'end',
157
+ direction: 'rtl',
158
+ },
159
+ },
160
+ itemNameFolderTable: {
161
+ fontWeight: 'bold',
162
+ },
163
+ itemSizeTable: {
164
+ display: 'inline-block',
165
+ width: 60,
166
+ verticalAlign: 'top',
167
+ textAlign: 'right',
168
+ whiteSpace: 'nowrap',
169
+ },
170
+ itemAccessTable: {
171
+ // display: 'inline-block',
172
+ verticalAlign: 'top',
173
+ width: 60,
174
+ textAlign: 'right',
175
+ paddingRight: 5,
176
+ display: 'flex',
177
+ justifyContent: 'center',
178
+ },
179
+ itemImageTable: {
180
+ display: 'inline-block',
181
+ width: 30,
182
+ marginTop: 1,
183
+ objectFit: 'contain',
184
+ maxHeight: 30,
185
+ },
186
+ itemNoImageTable: {
187
+ marginTop: 6,
188
+ },
189
+ itemIconTable: {
190
+ display: 'inline-block',
191
+ marginTop: 1,
192
+ width: 30,
193
+ height: 30,
194
+ },
195
+ itemFolderTable: {},
196
+ itemFolderTemp: {
197
+ opacity: 0.4,
198
+ },
199
+ itemFolderIconTable: (theme) => ({
200
+ marginTop: '1px',
201
+ marginLeft: '8px',
202
+ display: 'inline-block',
203
+ width: 30,
204
+ height: 30,
205
+ color: theme.palette.secondary.main || '#fbff7d',
206
+ }),
207
+ itemDownloadButtonTable: (theme) => ({
208
+ display: 'inline-block',
209
+ width: BUTTON_WIDTH,
210
+ height: ROW_HEIGHT,
211
+ minWidth: BUTTON_WIDTH,
212
+ verticalAlign: 'middle',
213
+ textAlign: 'center',
214
+ padding: 0,
215
+ borderRadius: `${BUTTON_WIDTH / 2}px`,
216
+ '&:hover': {
217
+ backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
218
+ },
219
+ '& span': {
220
+ pt: '9px',
221
+ },
222
+ '& svg': {
223
+ width: 14,
224
+ height: 14,
225
+ fontSize: '1rem',
226
+ mt: '-3px',
227
+ verticalAlign: 'middle',
228
+ color: theme.palette.mode === 'dark' ? '#EEE' : '#111',
229
+ },
230
+ }),
231
+ itemDownloadEmptyTable: {
232
+ display: 'inline-block',
233
+ width: BUTTON_WIDTH,
234
+ height: ROW_HEIGHT,
235
+ minWidth: BUTTON_WIDTH,
236
+ padding: 0,
237
+ },
238
+ itemAclButtonTable: {
239
+ width: BUTTON_WIDTH,
240
+ height: ROW_HEIGHT,
241
+ minWidth: BUTTON_WIDTH,
242
+ verticalAlign: 'top',
243
+ padding: 0,
244
+ fontSize: 12,
245
+ display: 'flex',
246
+ },
247
+ itemDeleteButtonTable: {
248
+ display: 'inline-block',
249
+ width: BUTTON_WIDTH,
250
+ height: ROW_HEIGHT,
251
+ minWidth: BUTTON_WIDTH,
252
+ verticalAlign: 'top',
253
+ padding: 0,
254
+ '& svg': {
255
+ width: 18,
256
+ height: 18,
257
+ fontSize: '1.5rem',
258
+ },
259
+ },
260
+ uploadDiv: {
261
+ top: 0,
262
+ zIndex: 1,
263
+ bottom: 0,
264
+ left: 0,
265
+ right: 0,
266
+ position: 'absolute',
267
+ opacity: 0.9,
268
+ textAlign: 'center',
269
+ background: '#FFFFFF',
270
+ },
271
+ uploadDivDragging: {
272
+ opacity: 1,
273
+ },
274
+ uploadCenterDiv: (theme) => ({
275
+ m: '20px',
276
+ border: '3px dashed grey',
277
+ borderRadius: '30px',
278
+ width: 'calc(100% - 40px)',
279
+ height: 'calc(100% - 40px)',
280
+ position: 'relative',
281
+ color: theme.palette.mode === 'dark' ? '#222' : '#CCC',
282
+ display: 'flex',
283
+ alignItems: 'center',
284
+ justifyContent: 'center',
285
+ }),
286
+ uploadCenterIcon: {
287
+ width: '25%',
288
+ height: '25%',
289
+ },
290
+ uploadCenterText: {
291
+ fontSize: 24,
292
+ fontWeight: 'bold',
293
+ },
294
+ uploadCloseButton: {
295
+ zIndex: 2,
296
+ position: 'absolute',
297
+ top: 30,
298
+ right: 30,
299
+ },
300
+ uploadCenterTextAndIcon: {
301
+ position: 'absolute',
302
+ height: '30%',
303
+ width: '100%',
304
+ margin: 'auto',
305
+ opacity: 0.3,
306
+ },
307
+ menuButtonExpertActive: {
308
+ color: '#c00000',
309
+ },
310
+ menuButtonRestrictActive: {
311
+ color: '#c05000',
312
+ },
313
+ pathDiv: (theme) => ({
314
+ display: 'flex',
315
+ width: 'calc(100% - 16px)',
316
+ ml: 1,
317
+ mr: 1,
318
+ textOverflow: 'clip',
319
+ overflow: 'hidden',
320
+ whiteSpace: 'nowrap',
321
+ backgroundColor: theme.palette.secondary.main,
322
+ color: theme.palette.secondary.contrastText,
323
+ borderRadius: '4px 4px 0 0',
324
+ }),
325
+ pathDivInput: {
326
+ width: '100%',
327
+ },
328
+ pathDivBreadcrumbDir: (theme) => ({
329
+ pl: '2px',
330
+ pr: '2px',
331
+ cursor: 'pointer',
332
+ color: 'white',
333
+ '&:hover': {
334
+ backgroundColor: theme.palette.primary.main,
335
+ color: theme.palette.primary.contrastText,
336
+ },
337
+ }),
338
+ pathDivBreadcrumbSelected: {
339
+ // todo: add style
340
+ color: '#FFF',
341
+ },
342
+ backgroundImageLight: {
343
+ background: 'white',
344
+ },
345
+ backgroundImageDark: {
346
+ background: 'black',
347
+ },
348
+ backgroundImageColored: {
349
+ background: 'silver',
350
+ },
351
+ specialFolder: (theme) => ({
352
+ color: theme.palette.mode === 'dark' ? '#229b0f' : '#5dd300',
353
+ }),
354
+ tooltip: {
355
+ pointerEvents: 'none',
356
+ },
357
+ };
358
+ const USER_DATA = '0_userdata.0';
359
+ function getParentDir(dir) {
360
+ const parts = (dir || '').split('/');
361
+ if (parts.length) {
362
+ parts.pop();
363
+ }
364
+ return parts.join('/');
365
+ }
366
+ function isFile(path) {
367
+ const ext = Utils.getFileExtension(path);
368
+ return !!(ext?.toLowerCase().match(/[a-z]+/) && ext.length < 5);
369
+ }
370
+ const TABLE = 'Table';
371
+ const TILE = 'Tile';
372
+ function sortFolders(a, b) {
373
+ if (a.folder && b.folder) {
374
+ return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
375
+ }
376
+ if (a.folder) {
377
+ return -1;
378
+ }
379
+ if (b.folder) {
380
+ return 1;
381
+ }
382
+ return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
383
+ }
384
+ export class FileBrowserClass extends Component {
385
+ imagePrefix;
386
+ levelPadding;
387
+ mounted;
388
+ suppressDeleteConfirm;
389
+ browseList;
390
+ browseListRunning;
391
+ initialReadFinished;
392
+ supportSubscribes;
393
+ _tempTimeout;
394
+ limitToObjectID = null;
395
+ limitToPath = null;
396
+ lastSelect = null;
397
+ setOpacityTimer = null;
398
+ cacheFoldersTimeout = null;
399
+ foldersLoading = null;
400
+ cacheFolders = null;
401
+ localStorage;
402
+ scrollPositions = {};
403
+ refFileDiv;
404
+ constructor(props) {
405
+ super(props);
406
+ this.localStorage = window._localStorage || window.localStorage;
407
+ const expandedStr = this.localStorage.getItem('files.expanded') || '[]';
408
+ this.refFileDiv = React.createRef();
409
+ if (this.props.limitPath) {
410
+ const parts = this.props.limitPath.split('/');
411
+ this.limitToObjectID = parts[0];
412
+ this.limitToPath = !parts.length ? null : parts.length === 1 && parts[0] === '' ? null : parts.join('/');
413
+ if (this.limitToPath && this.limitToPath.endsWith('/')) {
414
+ this.limitToPath.substring(0, this.limitToPath.length - 1);
415
+ }
416
+ }
417
+ let expanded;
418
+ try {
419
+ expanded = JSON.parse(expandedStr);
420
+ if (this.limitToPath) {
421
+ expanded = expanded.filter(id => id.startsWith(`${this.limitToPath}/`) ||
422
+ id === this.limitToPath ||
423
+ this.limitToPath?.startsWith(`${id}/`));
424
+ }
425
+ }
426
+ catch {
427
+ expanded = [];
428
+ }
429
+ let viewType;
430
+ if (this.props.showViewTypeButton) {
431
+ viewType = this.localStorage.getItem('files.viewType') || TABLE;
432
+ }
433
+ else {
434
+ viewType = TABLE;
435
+ }
436
+ let selected = this.props.selected || this.localStorage.getItem('files.selected') || USER_DATA;
437
+ let currentDir;
438
+ if (props.restrictToFolder) {
439
+ selected = props.restrictToFolder;
440
+ currentDir = props.restrictToFolder;
441
+ const parts = props.restrictToFolder.split('/');
442
+ expanded = [];
443
+ let path = '';
444
+ for (let i = 0; i < parts.length; i++) {
445
+ path += (path ? '/' : '') + parts[i];
446
+ expanded.push(path);
447
+ }
448
+ }
449
+ else {
450
+ // TODO: Now we do not support multiple selection
451
+ if (Array.isArray(selected)) {
452
+ selected = selected[0];
453
+ }
454
+ if (isFile(selected)) {
455
+ currentDir = getParentDir(selected);
456
+ }
457
+ else {
458
+ currentDir = selected;
459
+ }
460
+ }
461
+ const backgroundImage = this.localStorage.getItem('files.backgroundImage') || null;
462
+ this.state = {
463
+ viewType,
464
+ folders: {},
465
+ filterEmpty: this.localStorage.getItem('files.empty') !== 'false',
466
+ expanded,
467
+ currentDir,
468
+ expertMode: !!props.expertMode,
469
+ addFolder: false,
470
+ uploadFile: false,
471
+ deleteItem: '',
472
+ // marked: [],
473
+ viewer: '',
474
+ formatEditFile: '',
475
+ path: selected,
476
+ selected,
477
+ errorText: '',
478
+ modalEditOfAccess: false,
479
+ backgroundImage,
480
+ queueLength: 0,
481
+ loadAllFolders: false,
482
+ // allFoldersLoaded: false,
483
+ fileErrors: [],
484
+ filterByType: props.filterByType || window.localStorage.getItem('files.filterByType') || '',
485
+ showTypesMenu: null,
486
+ restrictToFolder: props.restrictToFolder || '',
487
+ pathFocus: false,
488
+ suppressDeleteConfirm: false,
489
+ };
490
+ this.imagePrefix = this.props.imagePrefix || './files/';
491
+ this.levelPadding = this.props.levelPadding || 20;
492
+ this.mounted = true;
493
+ this.suppressDeleteConfirm = 0;
494
+ this.browseList = [];
495
+ this.browseListRunning = false;
496
+ this.initialReadFinished = false;
497
+ this.supportSubscribes = null;
498
+ this._tempTimeout = {};
499
+ }
500
+ static getDerivedStateFromProps(props, state) {
501
+ if (props.expertMode !== undefined && props.expertMode !== state.expertMode) {
502
+ return { expertMode: props.expertMode, loadAllFolders: true };
503
+ }
504
+ return null;
505
+ }
506
+ async loadFolders() {
507
+ this.initialReadFinished = false;
508
+ let folders = (await this.browseFolder('/'));
509
+ if (this.state.viewType === TABLE) {
510
+ folders = (await this.browseFolders([...this.state.expanded], folders));
511
+ }
512
+ else if (this.state.currentDir &&
513
+ this.state.currentDir !== '/' &&
514
+ (!this.limitToObjectID || this.state.currentDir.startsWith(this.limitToObjectID))) {
515
+ folders = (await this.browseFolder(this.state.currentDir, folders));
516
+ }
517
+ this.setState({ folders }, () => {
518
+ if (this.state.viewType === TABLE && !this.findItem(this.state.selected)) {
519
+ const parts = this.state.selected.split('/');
520
+ while (parts.length && !this.findItem(parts.join('/'))) {
521
+ parts.pop();
522
+ }
523
+ let selected;
524
+ if (parts.length) {
525
+ selected = parts.join('/');
526
+ }
527
+ else {
528
+ selected = USER_DATA;
529
+ }
530
+ this.setState({ selected, path: selected, pathFocus: false }, () => this.scrollToSelected());
531
+ }
532
+ else {
533
+ this.scrollToSelected();
534
+ }
535
+ this.initialReadFinished = true;
536
+ });
537
+ }
538
+ scrollToSelected() {
539
+ if (this.mounted) {
540
+ const el = document.getElementById(this.state.selected);
541
+ el?.scrollIntoView();
542
+ }
543
+ }
544
+ async componentDidMount() {
545
+ this.mounted = true;
546
+ this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
547
+ this.browseList = [];
548
+ this.browseListRunning = false;
549
+ this.supportSubscribes = await this.props.socket.checkFeatureSupported('BINARY_STATE_EVENT');
550
+ if (this.supportSubscribes) {
551
+ await this.props.socket.subscribeFiles('*', '*', this.onFileChange);
552
+ }
553
+ }
554
+ componentWillUnmount() {
555
+ if (this.supportSubscribes) {
556
+ this.props.socket.unsubscribeFiles('*', '*', this.onFileChange);
557
+ }
558
+ this.mounted = false;
559
+ this.browseList = null;
560
+ this.browseListRunning = false;
561
+ Object.values(this._tempTimeout).forEach(timer => {
562
+ if (timer) {
563
+ clearTimeout(timer);
564
+ }
565
+ });
566
+ this._tempTimeout = {};
567
+ }
568
+ browseFoldersCb(foldersList, newFoldersNotNull, cb) {
569
+ if (!foldersList?.length) {
570
+ cb(newFoldersNotNull);
571
+ }
572
+ else {
573
+ const folder = foldersList.shift();
574
+ if (folder) {
575
+ void this.browseFolder(folder, newFoldersNotNull)
576
+ .catch((e) => console.error(`Cannot read folder ${folder}: ${e.message}`))
577
+ .then(() => {
578
+ setTimeout(() => this.browseFoldersCb(foldersList, newFoldersNotNull, cb), 0);
579
+ });
580
+ }
581
+ else {
582
+ setTimeout(() => this.browseFoldersCb(foldersList, newFoldersNotNull, cb), 0);
583
+ }
584
+ }
585
+ }
586
+ browseFolders(foldersList, _newFolders) {
587
+ let newFoldersNotNull;
588
+ if (!_newFolders) {
589
+ newFoldersNotNull = {};
590
+ Object.keys(this.state.folders).forEach(folder => (newFoldersNotNull[folder] = this.state.folders[folder]));
591
+ }
592
+ else {
593
+ newFoldersNotNull = _newFolders;
594
+ }
595
+ if (!foldersList?.length) {
596
+ return Promise.resolve(newFoldersNotNull);
597
+ }
598
+ return new Promise(resolve => {
599
+ this.browseFoldersCb(foldersList, newFoldersNotNull, resolve);
600
+ });
601
+ }
602
+ readDirSerial(adapter, relPath) {
603
+ return new Promise((resolve, reject) => {
604
+ if (this.browseList) {
605
+ // if component still mounted
606
+ this.browseList.push({
607
+ resolve: resolve,
608
+ reject,
609
+ adapter,
610
+ relPath,
611
+ });
612
+ if (!this.browseListRunning) {
613
+ this.processBrowseList();
614
+ }
615
+ }
616
+ });
617
+ }
618
+ processBrowseList(level = 0) {
619
+ if (!this.browseListRunning && this.browseList && this.browseList.length) {
620
+ this.browseListRunning = true;
621
+ if (this.browseList.length > 10) {
622
+ // not too often
623
+ if (!(this.browseList.length % 10)) {
624
+ this.setState({ queueLength: this.browseList.length });
625
+ }
626
+ }
627
+ else {
628
+ this.setState({ queueLength: this.browseList.length });
629
+ }
630
+ this.browseList[0].processing = true;
631
+ this.props.socket
632
+ .readDir(this.browseList[0].adapter, this.browseList[0].relPath || '')
633
+ .then(files => {
634
+ if (this.browseList) {
635
+ // if component still mounted
636
+ const item = this.browseList.shift();
637
+ if (item) {
638
+ const resolve = item.resolve;
639
+ item.resolve = null;
640
+ item.reject = null;
641
+ item.adapter = null;
642
+ item.relPath = null;
643
+ if (resolve) {
644
+ resolve(files);
645
+ }
646
+ this.browseListRunning = false;
647
+ if (this.browseList.length) {
648
+ if (level < 5) {
649
+ this.processBrowseList(level + 1);
650
+ }
651
+ else {
652
+ setTimeout(() => this.processBrowseList(0), 0);
653
+ }
654
+ }
655
+ else {
656
+ this.setState({ queueLength: 0 });
657
+ }
658
+ }
659
+ else {
660
+ this.setState({ queueLength: 0 });
661
+ }
662
+ }
663
+ })
664
+ .catch(e => {
665
+ if (this.browseList) {
666
+ // if component still mounted
667
+ const item = this.browseList.shift();
668
+ if (item) {
669
+ const reject = item.reject;
670
+ item.resolve = null;
671
+ item.reject = null;
672
+ item.adapter = null;
673
+ item.relPath = null;
674
+ if (reject) {
675
+ reject(e);
676
+ }
677
+ this.browseListRunning = false;
678
+ if (this.browseList.length) {
679
+ if (level < 5) {
680
+ this.processBrowseList(level + 1);
681
+ }
682
+ else {
683
+ setTimeout(() => this.processBrowseList(0), 0);
684
+ }
685
+ }
686
+ else {
687
+ this.setState({ queueLength: 0 });
688
+ }
689
+ }
690
+ else {
691
+ this.setState({ queueLength: 0 });
692
+ }
693
+ }
694
+ });
695
+ }
696
+ }
697
+ async browseFolder(folderId, _newFolders, _checkEmpty, force) {
698
+ let newFoldersNotNull;
699
+ if (!_newFolders) {
700
+ newFoldersNotNull = {};
701
+ Object.keys(this.state.folders).forEach(folder => {
702
+ newFoldersNotNull[folder] = this.state.folders[folder];
703
+ });
704
+ }
705
+ else {
706
+ newFoldersNotNull = _newFolders;
707
+ }
708
+ if (newFoldersNotNull[folderId] && !force) {
709
+ if (!_checkEmpty) {
710
+ return new Promise((resolve, reject) => {
711
+ Promise.all(newFoldersNotNull[folderId]
712
+ .filter(item => item.folder)
713
+ .map(item => this.browseFolder(item.id, newFoldersNotNull, true).catch(() => undefined)))
714
+ .then(() => resolve(newFoldersNotNull))
715
+ .catch((error) => reject(new Error(error)));
716
+ });
717
+ }
718
+ return Promise.resolve(newFoldersNotNull);
719
+ }
720
+ // if root folder
721
+ if (!folderId || folderId === '/') {
722
+ try {
723
+ let objs = (await this.props.socket.readMetaItems());
724
+ const _folders = [];
725
+ let userData = null;
726
+ if (this.state.restrictToFolder) {
727
+ const adapter = this.state.restrictToFolder.split('/')[0];
728
+ objs = objs.filter(obj => obj._id === adapter);
729
+ }
730
+ else if (!this.state.expertMode) {
731
+ // load only adapter.admin and not other meta files like hm-rpc.0.devices.blablabla
732
+ objs = objs.filter(obj => !obj._id.endsWith('.admin'));
733
+ }
734
+ const pos = objs.findIndex(obj => obj._id === 'system.meta.uuid');
735
+ if (pos !== -1) {
736
+ objs.splice(pos, 1);
737
+ }
738
+ objs.forEach(obj => {
739
+ if (this.limitToObjectID && this.limitToObjectID !== obj._id) {
740
+ return;
741
+ }
742
+ const item = {
743
+ id: obj._id,
744
+ name: obj._id,
745
+ title: (obj.common && obj.common.name) || obj._id,
746
+ meta: true,
747
+ from: obj.from,
748
+ ts: obj.ts,
749
+ color: obj.common && obj.common.color,
750
+ icon: obj.common && obj.common.icon,
751
+ folder: true,
752
+ acl: obj.acl,
753
+ level: 0,
754
+ };
755
+ if (item.id === USER_DATA) {
756
+ // user data must be first
757
+ userData = item;
758
+ }
759
+ else {
760
+ _folders.push(item);
761
+ }
762
+ });
763
+ _folders.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
764
+ if (!this.limitToObjectID || this.limitToObjectID === USER_DATA) {
765
+ if (userData) {
766
+ _folders.unshift(userData);
767
+ }
768
+ }
769
+ newFoldersNotNull[folderId || '/'] = _folders;
770
+ if (!_checkEmpty) {
771
+ return Promise.all(_folders
772
+ .filter(item => item.folder)
773
+ .map(item => this.browseFolder(item.id, newFoldersNotNull, true).catch(() => undefined))).then(() => newFoldersNotNull);
774
+ }
775
+ }
776
+ catch (e) {
777
+ const knownError = e;
778
+ if (this.initialReadFinished) {
779
+ window.alert(`Cannot read meta items: ${knownError.message}`);
780
+ }
781
+ newFoldersNotNull[folderId || '/'] = [];
782
+ }
783
+ return newFoldersNotNull;
784
+ }
785
+ const parts = folderId.split('/');
786
+ const level = parts.length;
787
+ const adapter = parts.shift();
788
+ const relPath = parts.join('/');
789
+ // make all requests here serial
790
+ let files;
791
+ try {
792
+ files = await this.readDirSerial(adapter || '', relPath);
793
+ }
794
+ catch (error) {
795
+ // work around: 0_userdata.0 is a special folder, that should exist event when other folders and itself do not exit, as the browser shows it anyway.
796
+ if (error === 'Not exists' && adapter === '0_userdata.0') {
797
+ files = [];
798
+ }
799
+ else {
800
+ throw error;
801
+ }
802
+ }
803
+ try {
804
+ const _folders = [];
805
+ files.forEach(file => {
806
+ const item = {
807
+ id: `${folderId}/${file.file}`,
808
+ ext: Utils.getFileExtension(file.file),
809
+ folder: file.isDir,
810
+ name: file.file,
811
+ size: file.stats?.size,
812
+ modified: file.modifiedAt,
813
+ acl: file.acl,
814
+ level,
815
+ };
816
+ if (this.state.restrictToFolder) {
817
+ if (item.folder &&
818
+ (item.id.startsWith(`${this.state.restrictToFolder}/`) ||
819
+ item.id === this.state.restrictToFolder ||
820
+ this.state.restrictToFolder.startsWith(`${item.id}/`))) {
821
+ _folders.push(item);
822
+ }
823
+ else if (item.id.startsWith(`${this.state.restrictToFolder}/`)) {
824
+ _folders.push(item);
825
+ }
826
+ }
827
+ else if (this.limitToPath) {
828
+ if (item.folder &&
829
+ (item.id.startsWith(`${this.limitToPath}/`) ||
830
+ item.id === this.limitToPath ||
831
+ this.limitToPath.startsWith(`${item.id}/`))) {
832
+ _folders.push(item);
833
+ }
834
+ else if (item.id.startsWith(`${this.limitToPath}/`)) {
835
+ _folders.push(item);
836
+ }
837
+ }
838
+ else {
839
+ _folders.push(item);
840
+ }
841
+ });
842
+ _folders.sort(sortFolders);
843
+ newFoldersNotNull[folderId] = _folders;
844
+ if (!_checkEmpty) {
845
+ return Promise.all(_folders
846
+ .filter(item => item.folder)
847
+ .map(item => this.browseFolder(item.id, newFoldersNotNull, true))).then(() => newFoldersNotNull);
848
+ }
849
+ }
850
+ catch (e) {
851
+ const knownError = e;
852
+ if (this.initialReadFinished) {
853
+ window.alert(`Cannot read ${adapter}${relPath ? `/${relPath}` : ''}: ${knownError?.message}`);
854
+ }
855
+ newFoldersNotNull[folderId] = [];
856
+ }
857
+ return newFoldersNotNull;
858
+ }
859
+ toggleFolder(item, e) {
860
+ e?.stopPropagation();
861
+ const expanded = [...this.state.expanded];
862
+ const pos = expanded.indexOf(item.id);
863
+ if (pos === -1) {
864
+ expanded.push(item.id);
865
+ expanded.sort();
866
+ this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
867
+ if (!item.temp) {
868
+ this.browseFolder(item.id)
869
+ .then(folders => this.setState({ expanded, folders }))
870
+ .catch(err => window.alert(err === NOT_FOUND
871
+ ? this.props.t('ra_Cannot find "%s"', item.id)
872
+ : this.props.t('ra_Cannot read "%s"', item.id)));
873
+ }
874
+ else {
875
+ this.setState({ expanded });
876
+ }
877
+ }
878
+ else {
879
+ expanded.splice(pos, 1);
880
+ this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
881
+ this.setState({ expanded });
882
+ }
883
+ }
884
+ onFileChange = (id, fileName, size) => {
885
+ const key = `${id}/${fileName}`;
886
+ const pos = key.lastIndexOf('/');
887
+ const folder = key.substring(0, pos);
888
+ console.log(`File changed ${key}[${size}]`);
889
+ if (this.state.folders[folder]) {
890
+ if (this._tempTimeout[folder]) {
891
+ clearTimeout(this._tempTimeout[folder]);
892
+ }
893
+ this._tempTimeout[folder] = setTimeout(() => {
894
+ delete this._tempTimeout[folder];
895
+ this.browseFolder(folder, null, false, true)
896
+ .then(folders => this.setState({ folders }))
897
+ .catch(e => console.error(`Cannot read folder: ${e.message}`));
898
+ }, 300);
899
+ }
900
+ };
901
+ changeFolder(e, folder) {
902
+ e?.stopPropagation();
903
+ this.lastSelect = Date.now();
904
+ let _folder = folder || getParentDir(this.state.currentDir);
905
+ if (_folder === '/') {
906
+ _folder = '';
907
+ }
908
+ // remember scroll position for this folder
909
+ if (this.state.viewType === 'Tile' && this.refFileDiv.current?.scrollTop) {
910
+ this.scrollPositions[this.state.currentDir] = this.refFileDiv.current.scrollTop;
911
+ }
912
+ this.localStorage.setItem('files.currentDir', _folder);
913
+ if (folder && e && (e.altKey || e.shiftKey || e.ctrlKey || e.metaKey)) {
914
+ this.setState({ selected: _folder });
915
+ return;
916
+ }
917
+ // If desired folder is not yet loaded
918
+ if (_folder && !this.state.folders[_folder]) {
919
+ this.browseFolder(_folder)
920
+ .then(folders => this.setState({
921
+ folders,
922
+ path: _folder,
923
+ currentDir: _folder,
924
+ selected: _folder,
925
+ pathFocus: false,
926
+ }, () => this.props.onSelect && this.props.onSelect('')))
927
+ .catch(_e => console.error(`Cannot read folder: ${_e.message}`));
928
+ return;
929
+ }
930
+ this.setState({
931
+ currentDir: _folder,
932
+ selected: _folder,
933
+ path: _folder,
934
+ pathFocus: false,
935
+ }, () => {
936
+ if (this.props.onSelect) {
937
+ this.props.onSelect('');
938
+ }
939
+ // scroll to previous position
940
+ if (this.state.viewType === 'Tile' && this.scrollPositions[this.state.currentDir]) {
941
+ const pos = this.scrollPositions[this.state.currentDir];
942
+ delete this.scrollPositions[this.state.currentDir];
943
+ if (this.refFileDiv.current) {
944
+ this.refFileDiv.current.scrollTop = pos;
945
+ }
946
+ }
947
+ });
948
+ }
949
+ select(id, e, cb) {
950
+ if (e) {
951
+ e.stopPropagation();
952
+ }
953
+ this.lastSelect = Date.now();
954
+ this.localStorage.setItem('files.selected', id);
955
+ this.setState({ selected: id, path: id, pathFocus: false }, () => {
956
+ if (this.props.onSelect) {
957
+ const ext = Utils.getFileExtension(id);
958
+ if ((!this.props.filterFiles || (ext && this.props.filterFiles.includes(ext))) &&
959
+ (!this.state.filterByType ||
960
+ (ext && EXTENSIONS[this.state.filterByType].includes(ext)))) {
961
+ this.props.onSelect(id, false, !!this.state.folders[id]);
962
+ }
963
+ else {
964
+ this.props.onSelect('');
965
+ }
966
+ }
967
+ if (cb) {
968
+ cb();
969
+ }
970
+ });
971
+ }
972
+ getText(text) {
973
+ if (text) {
974
+ if (typeof text === 'object') {
975
+ return text[this.props.lang] || text.en || undefined;
976
+ }
977
+ return text;
978
+ }
979
+ return undefined;
980
+ }
981
+ renderFolder(item, expanded) {
982
+ if (
983
+ // this.state.viewType === TABLE &&
984
+ this.state.filterEmpty &&
985
+ !this.state.folders[item.id]?.length && // if the folder is empty
986
+ item.id !== USER_DATA &&
987
+ !item.temp) {
988
+ return null;
989
+ }
990
+ const IconEl = expanded ? IconOpen : IconClosed;
991
+ const padding = this.state.viewType === TABLE ? item.level * this.levelPadding : 0;
992
+ const isUserData = item.name === USER_DATA;
993
+ const isSpecialData = isUserData || item.name === 'vis.0' || item.name === 'vis-2.0';
994
+ const iconStyle = Utils.getStyle(this.props.theme, styles[`itemFolderIcon${this.state.viewType}`], isSpecialData && styles.specialFolder);
995
+ return (React.createElement(Box, { component: "div", key: item.id, id: item.id, style: this.state.viewType === TABLE
996
+ ? { marginLeft: padding, width: `calc(100% - ${padding}px)` }
997
+ : undefined, onClick: e => (this.state.viewType === TABLE ? this.select(item.id, e) : this.changeFolder(e, item.id)), onDoubleClick: e => this.state.viewType === TABLE && this.toggleFolder(item, e), title: this.getText(item.title), className: "browserItem", sx: Utils.getStyle(this.props.theme, styles[`item${this.state.viewType}`], styles[`itemFolder${this.state.viewType}`], this.state.selected === item.id ? styles.itemSelected : {}, item.temp ? styles.itemFolderTemp : {}) },
998
+ React.createElement(IconEl, { style: iconStyle, onClick: this.state.viewType === TABLE ? (e) => this.toggleFolder(item, e) : undefined }),
999
+ React.createElement(Box, { component: "div", sx: Utils.getStyle(this.props.theme, styles[`itemName${this.state.viewType}`], styles[`itemNameFolder${this.state.viewType}`]) }, isUserData ? this.props.t('ra_User files') : item.name),
1000
+ React.createElement(Box, { component: "div", style: styles[`itemSize${this.state.viewType}`], sx: { display: { md: 'inline-block', sm: 'none' } } }, this.state.viewType === TABLE && this.state.folders[item.id]
1001
+ ? this.state.folders[item.id].length
1002
+ : ''),
1003
+ React.createElement(Box, { component: "div", sx: { display: { md: 'inline-block', sm: 'none' } } }, this.state.viewType === TABLE && this.props.expertMode ? this.formatAcl(item.acl) : null),
1004
+ this.state.viewType === TABLE && this.props.expertMode ? (React.createElement(Box, { component: "div", sx: { ...styles.itemDeleteButtonTable, display: { md: 'inline-block', sm: 'none' } } })) : null,
1005
+ this.state.viewType === TABLE && this.props.allowDownload ? (React.createElement("div", { style: styles[`itemDownloadEmpty${this.state.viewType}`] })) : null,
1006
+ this.state.viewType === TABLE &&
1007
+ this.props.allowDelete &&
1008
+ this.state.folders[item.id] &&
1009
+ this.state.folders[item.id].length ? (React.createElement(IconButton, { "aria-label": "delete", onClick: e => {
1010
+ e.stopPropagation();
1011
+ if (this.suppressDeleteConfirm > Date.now()) {
1012
+ this.deleteItem(item.id);
1013
+ }
1014
+ else {
1015
+ this.setState({ deleteItem: item.id });
1016
+ }
1017
+ }, sx: styles[`itemDeleteButton${this.state.viewType}`], size: "large" },
1018
+ React.createElement(DeleteIcon, { fontSize: "small" }))) : this.state.viewType === TABLE && this.props.allowDelete ? (React.createElement(Box, { component: "div", sx: styles[`itemDeleteButton${this.state.viewType}`] })) : null));
1019
+ }
1020
+ renderBackFolder() {
1021
+ return (React.createElement(Box, { component: "div", key: this.state.currentDir, id: this.state.currentDir, onClick: e => this.changeFolder(e), title: this.props.t('ra_Back to %s', getParentDir(this.state.currentDir)), className: "browserItem", sx: Utils.getStyle(this.props.theme, styles[`item${this.state.viewType}`], styles[`itemFolder${this.state.viewType}`]) },
1022
+ React.createElement(IconClosed, { style: Utils.getStyle(this.props.theme, styles[`itemFolderIcon${this.state.viewType}`]) }),
1023
+ React.createElement(IconBack, { sx: styles.itemFolderIconBack }),
1024
+ React.createElement(Box, { component: "div", sx: Utils.getStyle(this.props.theme, styles[`itemName${this.state.viewType}`], styles[`itemNameFolder${this.state.viewType}`]) }, "..")));
1025
+ }
1026
+ formatSize(size) {
1027
+ return (React.createElement("div", { style: styles[`itemSize${this.state.viewType}`] }, size || size === 0 ? Utils.formatBytes(size) : ''));
1028
+ }
1029
+ formatAcl(acl) {
1030
+ const access = acl ? acl.permissions || acl.file : 0;
1031
+ let accessStr;
1032
+ if (access) {
1033
+ accessStr = access.toString(16).padStart(3, '0');
1034
+ }
1035
+ else {
1036
+ accessStr = '';
1037
+ }
1038
+ return (React.createElement("div", { style: styles[`itemAccess${this.state.viewType}`] }, this.props.modalEditOfAccessControl ? (React.createElement(IconButton, { size: "large", onClick: () => this.setState({ modalEditOfAccess: true }), sx: styles[`itemAclButton${this.state.viewType}`] }, accessStr || '---')) : (accessStr || '---')));
1039
+ }
1040
+ getFileIcon(ext) {
1041
+ switch (ext) {
1042
+ case 'json':
1043
+ case 'json5':
1044
+ return React.createElement(JsonIcon, { style: styles[`itemIcon${this.state.viewType}`] });
1045
+ case 'css':
1046
+ return React.createElement(CssIcon, { style: styles[`itemIcon${this.state.viewType}`] });
1047
+ case 'js':
1048
+ case 'ts':
1049
+ return React.createElement(JSIcon, { style: styles[`itemIcon${this.state.viewType}`] });
1050
+ case 'html':
1051
+ case 'md':
1052
+ return React.createElement(HtmlIcon, { style: styles[`itemIcon${this.state.viewType}`] });
1053
+ case 'mp3':
1054
+ case 'ogg':
1055
+ case 'wav':
1056
+ case 'm4a':
1057
+ case 'mp4':
1058
+ case 'flac':
1059
+ return React.createElement(MusicIcon, { style: styles[`itemIcon${this.state.viewType}`] });
1060
+ default:
1061
+ return React.createElement(FileIcon, { style: styles[`itemIcon${this.state.viewType}`] });
1062
+ }
1063
+ }
1064
+ static getEditFile(ext) {
1065
+ switch (ext) {
1066
+ case 'json':
1067
+ case 'json5':
1068
+ case 'js':
1069
+ case 'html':
1070
+ case 'txt':
1071
+ case 'css':
1072
+ case 'log':
1073
+ case 'csv':
1074
+ return true;
1075
+ default:
1076
+ return false;
1077
+ }
1078
+ }
1079
+ setStateBackgroundImage = () => {
1080
+ const array = ['light', 'dark', 'colored', 'delete'];
1081
+ this.setState(({ backgroundImage }) => {
1082
+ if (backgroundImage &&
1083
+ array.indexOf(backgroundImage) !== -1 &&
1084
+ array.length - 1 !== array.indexOf(backgroundImage)) {
1085
+ this.localStorage.setItem('files.backgroundImage', array[array.indexOf(backgroundImage) + 1]);
1086
+ return { backgroundImage: array[array.indexOf(backgroundImage) + 1] };
1087
+ }
1088
+ this.localStorage.setItem('files.backgroundImage', array[0]);
1089
+ return { backgroundImage: array[0] };
1090
+ });
1091
+ };
1092
+ getStyleBackgroundImage = () => {
1093
+ // ['light', 'dark', 'colored', 'delete']
1094
+ switch (this.state.backgroundImage) {
1095
+ case 'light':
1096
+ return styles.backgroundImageLight;
1097
+ case 'dark':
1098
+ return styles.backgroundImageDark;
1099
+ case 'colored':
1100
+ return styles.backgroundImageColored;
1101
+ case 'delete':
1102
+ return null;
1103
+ default:
1104
+ return null;
1105
+ }
1106
+ };
1107
+ renderFile(item) {
1108
+ const padding = this.state.viewType === TABLE ? item.level * this.levelPadding : 0;
1109
+ const ext = Utils.getFileExtension(item.name);
1110
+ return (React.createElement(Box, { component: "div", key: item.id, id: item.id, onDoubleClick: e => {
1111
+ e.stopPropagation();
1112
+ if (!this.props.onSelect) {
1113
+ this.setState({ viewer: this.imagePrefix + item.id, formatEditFile: ext });
1114
+ }
1115
+ else if ((!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1116
+ (!this.state.filterByType ||
1117
+ (item.ext &&
1118
+ EXTENSIONS[this.state.filterByType].includes(item.ext)))) {
1119
+ this.props.onSelect(item.id, true, !!this.state.folders[item.id]);
1120
+ }
1121
+ }, onClick: e => this.select(item.id, e), style: this.state.viewType === TABLE
1122
+ ? { marginLeft: padding, width: `calc(100% - ${padding}px)` }
1123
+ : undefined, className: "browserItem", sx: Utils.getStyle(this.props.theme, styles[`item${this.state.viewType}`], styles[`itemFile${this.state.viewType}`], this.state.selected === item.id ? styles.itemSelected : undefined) },
1124
+ ext && EXTENSIONS.images.includes(ext) ? (this.state.fileErrors.includes(item.id) ? (React.createElement(IconNoIcon, { style: {
1125
+ ...styles[`itemImage${this.state.viewType}`],
1126
+ ...this.getStyleBackgroundImage(),
1127
+ ...styles[`itemNoImage${this.state.viewType}`],
1128
+ } })) : (React.createElement(Icon, { onError: e => {
1129
+ e.target.onerror = null;
1130
+ const fileErrors = [...this.state.fileErrors];
1131
+ if (!fileErrors.includes(item.id)) {
1132
+ fileErrors.push(item.id);
1133
+ this.setState({ fileErrors });
1134
+ }
1135
+ }, style: { ...styles[`itemImage${this.state.viewType}`], ...this.getStyleBackgroundImage() }, src: this.imagePrefix + item.id, alt: item.name }))) : (this.getFileIcon(ext)),
1136
+ React.createElement(Box, { component: "div", sx: styles[`itemName${this.state.viewType}`] }, item.name),
1137
+ React.createElement(Box, { component: "div", sx: { display: { md: 'inline-block', sm: 'none' } } }, this.formatSize(item.size)),
1138
+ React.createElement(Box, { component: "div", sx: { display: { md: 'inline-block', sm: 'none' } } }, this.state.viewType === TABLE && this.props.expertMode ? this.formatAcl(item.acl) : null),
1139
+ React.createElement(Box, { component: "div", sx: { display: { md: 'inline-block', sm: 'none' } } }, this.state.viewType === TABLE && this.props.expertMode && FileBrowserClass.getEditFile(ext) ? (React.createElement(IconButton, { "aria-label": "edit", onClick: e => {
1140
+ e.stopPropagation();
1141
+ if (!this.props.onSelect) {
1142
+ this.setState({ viewer: this.imagePrefix + item.id, formatEditFile: ext });
1143
+ }
1144
+ else if ((!this.props.filterFiles ||
1145
+ (item.ext && this.props.filterFiles.includes(item.ext))) &&
1146
+ (!this.state.filterByType ||
1147
+ (item.ext &&
1148
+ EXTENSIONS[this.state.filterByType].includes(item.ext)))) {
1149
+ this.props.onSelect(item.id, true, !!this.state.folders[item.id]);
1150
+ }
1151
+ }, sx: styles.itemDeleteButtonTable, size: "large" },
1152
+ React.createElement(EditIcon, { fontSize: "small" }))) : (React.createElement(Box, { component: "div", sx: styles[`itemDeleteButton${this.state.viewType}`] }))),
1153
+ this.state.viewType === TABLE && this.props.allowDownload ? (React.createElement(Box, { component: "a", className: "MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge", sx: styles.itemDownloadButtonTable, tabIndex: 0, download: item.id, href: this.imagePrefix + item.id, onClick: e => e.stopPropagation() },
1154
+ React.createElement(DownloadIcon, null))) : null,
1155
+ this.state.viewType === TABLE &&
1156
+ this.props.allowDelete &&
1157
+ item.id !== 'vis.0/' &&
1158
+ item.id !== 'vis-2.0/' &&
1159
+ item.id !== USER_DATA ? (React.createElement(IconButton, { "aria-label": "delete", onClick: e => {
1160
+ e.stopPropagation();
1161
+ if (this.suppressDeleteConfirm > Date.now()) {
1162
+ this.deleteItem(item.id);
1163
+ }
1164
+ else {
1165
+ this.setState({ deleteItem: item.id });
1166
+ }
1167
+ }, sx: styles[`itemDeleteButton${this.state.viewType}`], size: "large" },
1168
+ React.createElement(DeleteIcon, { fontSize: "small" }))) : this.state.viewType === TABLE && this.props.allowDelete ? (React.createElement(Box, { component: "div", sx: styles[`itemDeleteButton${this.state.viewType}`] })) : null));
1169
+ }
1170
+ renderItems(folderId) {
1171
+ if (this.state.folders?.[folderId]) {
1172
+ // tile
1173
+ if (this.state.viewType === TILE) {
1174
+ const res = [];
1175
+ if (folderId && folderId !== '/') {
1176
+ res.push(this.renderBackFolder());
1177
+ }
1178
+ this.state.folders[folderId].forEach(item => {
1179
+ if (item.folder) {
1180
+ res.push(this.renderFolder(item));
1181
+ }
1182
+ else if ((!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1183
+ (!this.state.filterByType ||
1184
+ (item.ext &&
1185
+ EXTENSIONS[this.state.filterByType].includes(item.ext)))) {
1186
+ res.push(this.renderFile(item));
1187
+ }
1188
+ });
1189
+ return res;
1190
+ }
1191
+ // table
1192
+ const totalResult = [];
1193
+ this.state.folders[folderId].forEach(item => {
1194
+ if (item.folder) {
1195
+ const expanded = this.state.expanded.includes(item.id);
1196
+ const folders = this.renderFolder(item, expanded);
1197
+ if (Array.isArray(folders)) {
1198
+ folders.forEach(folder => totalResult.push(folder));
1199
+ }
1200
+ else {
1201
+ totalResult.push(folders);
1202
+ }
1203
+ if (this.state.folders[item.id] && expanded) {
1204
+ const items = this.renderItems(item.id);
1205
+ if (Array.isArray(items)) {
1206
+ items.forEach(_item => totalResult.push(_item));
1207
+ }
1208
+ else {
1209
+ totalResult.push(items);
1210
+ }
1211
+ }
1212
+ }
1213
+ else if ((!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1214
+ (!this.state.filterByType ||
1215
+ (item.ext &&
1216
+ EXTENSIONS[this.state.filterByType].includes(item.ext)))) {
1217
+ totalResult.push(this.renderFile(item));
1218
+ }
1219
+ });
1220
+ return totalResult;
1221
+ }
1222
+ return (React.createElement("div", { style: { position: 'relative' } },
1223
+ React.createElement(CircularProgress, { key: folderId, color: "secondary", size: 24 }),
1224
+ React.createElement("div", { style: {
1225
+ position: 'absolute',
1226
+ zIndex: 2,
1227
+ top: 4,
1228
+ width: 24,
1229
+ textAlign: 'center',
1230
+ } }, this.state.queueLength)));
1231
+ }
1232
+ renderToolbar() {
1233
+ const IconType = this.props.showTypeSelector
1234
+ ? FILE_TYPE_ICONS[this.state.filterByType || 'all'] || FILE_TYPE_ICONS.all
1235
+ : null;
1236
+ const isInFolder = this.findFirstFolder(this.state.selected);
1237
+ return (React.createElement(Toolbar, { key: "toolbar", variant: "dense" },
1238
+ this.props.allowNonRestricted && this.props.restrictToFolder ? (React.createElement(IconButton, { edge: "start", title: this.state.restrictToFolder
1239
+ ? this.props.t('ra_Show all folders')
1240
+ : this.props.t('ra_Restrict to folder'), style: {
1241
+ ...styles.menuButton,
1242
+ ...(this.state.restrictToFolder ? styles.menuButtonRestrictActive : undefined),
1243
+ }, "aria-label": "restricted to folder", onClick: () => this.setState({
1244
+ restrictToFolder: (this.state.restrictToFolder ? '' : this.props.restrictToFolder) || '',
1245
+ loadAllFolders: true,
1246
+ }), size: "small" },
1247
+ React.createElement(RestrictedIcon, { fontSize: "small" }))) : null,
1248
+ this.props.showExpertButton ? (React.createElement(IconButton, { edge: "start", title: this.props.t('ra_Toggle expert mode'), style: {
1249
+ ...styles.menuButton,
1250
+ ...(this.state.expertMode ? styles.menuButtonExpertActive : undefined),
1251
+ }, "aria-label": "expert mode", onClick: () => this.setState({ expertMode: !this.state.expertMode }), size: "small" },
1252
+ React.createElement(IconExpert, null))) : null,
1253
+ this.props.showViewTypeButton ? (React.createElement(IconButton, { edge: "start", title: this.props.t('ra_Toggle view mode'), style: styles.menuButton, "aria-label": "view mode", onClick: () => {
1254
+ const viewType = this.state.viewType === TABLE ? TILE : TABLE;
1255
+ this.localStorage.setItem('files.viewType', viewType);
1256
+ let currentDir = this.state.selected;
1257
+ if (isFile(currentDir)) {
1258
+ currentDir = getParentDir(currentDir);
1259
+ }
1260
+ this.setState({ viewType, currentDir }, () => {
1261
+ if (this.state.viewType === TABLE) {
1262
+ this.scrollToSelected();
1263
+ }
1264
+ });
1265
+ }, size: "small" }, this.state.viewType !== TABLE ? React.createElement(IconList, { fontSize: "small" }) : React.createElement(IconTile, { fontSize: "small" }))) : null,
1266
+ React.createElement(IconButton, { edge: "start", title: this.props.t('ra_Hide empty folders'), style: styles.menuButton, color: this.state.filterEmpty ? 'secondary' : 'inherit', "aria-label": "filter empty", onClick: () => {
1267
+ this.localStorage.setItem('file.empty', this.state.filterEmpty ? 'false' : 'true');
1268
+ this.setState({ filterEmpty: !this.state.filterEmpty });
1269
+ }, size: "small" },
1270
+ React.createElement(EmptyFilterIcon, { fontSize: "small" })),
1271
+ React.createElement(IconButton, { edge: "start", title: this.props.t('ra_Reload files'), style: styles.menuButton, color: "inherit", "aria-label": "reload files", onClick: () => this.setState({ folders: {} }, () => this.loadFolders()), size: "small" },
1272
+ React.createElement(RefreshIcon, { fontSize: "small" })),
1273
+ this.props.allowCreateFolder ? (React.createElement(IconButton, { edge: "start", disabled: !this.state.selected ||
1274
+ !isInFolder ||
1275
+ (!!this.limitToPath &&
1276
+ !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1277
+ this.limitToPath !== this.state.selected), title: this.props.t('ra_Create folder'), style: styles.menuButton, color: "inherit", "aria-label": "add folder", onClick: () => this.setState({ addFolder: true }), size: "small" },
1278
+ React.createElement(AddFolderIcon, { fontSize: "small" }))) : null,
1279
+ this.props.allowUpload ? (React.createElement(IconButton, { edge: "start", disabled: !this.state.selected ||
1280
+ !isInFolder ||
1281
+ (!!this.limitToPath &&
1282
+ !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1283
+ this.limitToPath !== this.state.selected), title: this.props.t('ra_Upload file'), style: styles.menuButton, color: "inherit", "aria-label": "upload file", onClick: () => this.setState({ uploadFile: true }), size: "small" },
1284
+ React.createElement(UploadIcon, { fontSize: "small" }))) : null,
1285
+ this.props.showTypeSelector && IconType ? (React.createElement(Tooltip, { title: this.props.t('ra_Filter files'), slotProps: { popper: { sx: styles.tooltip } } },
1286
+ React.createElement(IconButton, { size: "small", onClick: e => this.setState({ showTypesMenu: e.target }) },
1287
+ React.createElement(IconType, { fontSize: "small" })))) : null,
1288
+ this.state.showTypesMenu ? (React.createElement(Menu, { open: !0, anchorEl: this.state.showTypesMenu, onClose: () => this.setState({ showTypesMenu: null }) }, Object.keys(FILE_TYPE_ICONS).map(type => {
1289
+ const MyIcon = FILE_TYPE_ICONS[type];
1290
+ return (React.createElement(MenuItem, { key: type, selected: this.state.filterByType === type, onClick: () => {
1291
+ if (type === 'all') {
1292
+ this.localStorage.removeItem('files.filterByType');
1293
+ this.setState({ filterByType: '', showTypesMenu: null });
1294
+ }
1295
+ else {
1296
+ this.localStorage.setItem('files.filterByType', type);
1297
+ this.setState({ filterByType: type, showTypesMenu: null });
1298
+ }
1299
+ } },
1300
+ React.createElement(ListItemIcon, null,
1301
+ React.createElement(MyIcon, { fontSize: "small" })),
1302
+ React.createElement(ListItemText, null, this.props.t(`ra_fileType_${type}`))));
1303
+ }))) : null,
1304
+ React.createElement(Tooltip, { title: this.props.t('ra_Background image'), slotProps: { popper: { sx: styles.tooltip } } },
1305
+ React.createElement(IconButton, { color: "inherit", edge: "start", style: styles.menuButton, onClick: this.setStateBackgroundImage, size: "small" },
1306
+ React.createElement(Brightness5Icon, { fontSize: "small" }))),
1307
+ this.state.viewType !== TABLE && this.props.allowDelete ? (React.createElement(Tooltip, { title: this.props.t('ra_Delete'), slotProps: { popper: { sx: styles.tooltip } } },
1308
+ React.createElement("span", null,
1309
+ React.createElement(IconButton, { "aria-label": "delete", disabled: !this.state.selected ||
1310
+ this.state.selected === 'vis.0/' ||
1311
+ this.state.selected === 'vis-2.0/' ||
1312
+ this.state.selected === USER_DATA, color: "inherit", edge: "start", style: styles.menuButton, onClick: e => {
1313
+ e.stopPropagation();
1314
+ if (this.suppressDeleteConfirm > Date.now()) {
1315
+ this.deleteItem(this.state.selected);
1316
+ }
1317
+ else {
1318
+ this.setState({ deleteItem: this.state.selected });
1319
+ }
1320
+ }, size: "small" },
1321
+ React.createElement(DeleteIcon, { fontSize: "small" }))))) : null));
1322
+ }
1323
+ findItem(id, folders) {
1324
+ folders = folders || this.state.folders;
1325
+ if (!folders) {
1326
+ return null;
1327
+ }
1328
+ const parts = id.split('/');
1329
+ parts.pop();
1330
+ const parentFolder = parts.join('/') || '/';
1331
+ if (!folders[parentFolder]) {
1332
+ return null;
1333
+ }
1334
+ return folders[parentFolder].find(item => item.id === id) || null;
1335
+ }
1336
+ renderInputDialog() {
1337
+ if (this.state.addFolder) {
1338
+ const parentFolder = this.findFirstFolder(this.state.selected);
1339
+ if (!parentFolder) {
1340
+ window.alert(this.props.t('ra_Invalid parent folder!'));
1341
+ return null;
1342
+ }
1343
+ return (React.createElement(DialogTextInput, { key: "inputDialog", applyText: this.props.t('ra_Create'), cancelText: this.props.t('ra_Cancel'), titleText: this.props.t('ra_Create new folder in %s', this.state.selected), promptText: this.props.t('ra_If no file will be created in the folder, it will disappear after the browser closed'), labelText: this.props.t('ra_Folder name'), verify: (text) => this.state.folders[parentFolder].find(item => item.name === text)
1344
+ ? ''
1345
+ : this.props.t('ra_Duplicate name'), onClose: (name) => {
1346
+ if (name) {
1347
+ const folders = {};
1348
+ Object.keys(this.state.folders).forEach(folder => (folders[folder] = this.state.folders[folder]));
1349
+ const parent = this.findItem(parentFolder);
1350
+ const id = `${parentFolder}/${name}`;
1351
+ folders[parentFolder].push({
1352
+ id,
1353
+ level: (parent?.level || 0) + 1,
1354
+ name,
1355
+ folder: true,
1356
+ temp: true,
1357
+ });
1358
+ folders[parentFolder].sort(sortFolders);
1359
+ folders[id] = [];
1360
+ const expanded = [...this.state.expanded];
1361
+ if (!expanded.includes(parentFolder)) {
1362
+ expanded.push(parentFolder);
1363
+ expanded.sort();
1364
+ }
1365
+ this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
1366
+ this.setState({ addFolder: false, folders, expanded }, () => this.select(id));
1367
+ }
1368
+ else {
1369
+ this.setState({ addFolder: false });
1370
+ }
1371
+ }, replace: (text) => text.replace(/[^-_\w]/, '_') }));
1372
+ }
1373
+ return null;
1374
+ }
1375
+ componentDidUpdate( /* prevProps , prevState, snapshot */) {
1376
+ if (this.setOpacityTimer) {
1377
+ clearTimeout(this.setOpacityTimer);
1378
+ }
1379
+ this.setOpacityTimer = setTimeout(() => {
1380
+ this.setOpacityTimer = null;
1381
+ const items = window.document.getElementsByClassName('browserItem');
1382
+ for (let i = 0; i < items.length; i++) {
1383
+ items[i].style.opacity = '1';
1384
+ }
1385
+ }, 100);
1386
+ }
1387
+ findFirstFolder(id) {
1388
+ let parentFolder = id;
1389
+ const item = this.findItem(parentFolder);
1390
+ // find folder
1391
+ if (item && !item.folder) {
1392
+ const parts = parentFolder.split('/');
1393
+ parts.pop();
1394
+ parentFolder = '';
1395
+ while (parts.length) {
1396
+ const _item = this.findItem(parts.join('/'));
1397
+ if (_item?.folder) {
1398
+ parentFolder = parts.join('/');
1399
+ break;
1400
+ }
1401
+ parts.pop();
1402
+ }
1403
+ if (!parts.length) {
1404
+ return null;
1405
+ }
1406
+ }
1407
+ return parentFolder;
1408
+ }
1409
+ async uploadFile(fileName, data) {
1410
+ const parts = fileName.split('/');
1411
+ const adapterName = parts.shift();
1412
+ try {
1413
+ await this.props.socket.writeFile64(adapterName || '', parts.join('/'), data);
1414
+ }
1415
+ catch (e) {
1416
+ const knownError = e;
1417
+ window.alert(`Cannot write file: ${knownError?.message}`);
1418
+ }
1419
+ }
1420
+ renderUpload() {
1421
+ if (this.state.uploadFile) {
1422
+ return [
1423
+ React.createElement(Fab, { key: "close", color: "primary", "aria-label": "close", style: styles.uploadCloseButton, onClick: () => this.setState({ uploadFile: false }) },
1424
+ React.createElement(CloseIcon, null)),
1425
+ React.createElement(Dropzone, { key: "dropzone", onDragEnter: () => this.setState({ uploadFile: 'dragging' }), onDragLeave: () => this.setState({ uploadFile: true }), onDrop: acceptedFiles => {
1426
+ let count = acceptedFiles.length;
1427
+ acceptedFiles.forEach(file => {
1428
+ const reader = new FileReader();
1429
+ reader.onabort = () => console.log('file reading was aborted');
1430
+ reader.onerror = () => console.log('file reading has failed');
1431
+ reader.onload = () => {
1432
+ const parentFolder = this.findFirstFolder(this.state.selected);
1433
+ if (!parentFolder) {
1434
+ window.alert(this.props.t('ra_Invalid parent folder!'));
1435
+ }
1436
+ else {
1437
+ const id = `${parentFolder}/${file.name}`;
1438
+ void this.uploadFile(id, reader.result).then(() => {
1439
+ if (!--count) {
1440
+ this.setState({ uploadFile: false }, () => {
1441
+ if (this.supportSubscribes) {
1442
+ // open current folder
1443
+ const expanded = [...this.state.expanded];
1444
+ if (!expanded.includes(parentFolder)) {
1445
+ expanded.push(parentFolder);
1446
+ expanded.sort();
1447
+ this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
1448
+ }
1449
+ this.setState({ expanded }, () => this.select(id));
1450
+ }
1451
+ else {
1452
+ setTimeout(() => this.browseFolder(parentFolder, null, false, true).then(folders => {
1453
+ // open current folder
1454
+ const expanded = [...this.state.expanded];
1455
+ if (!expanded.includes(parentFolder)) {
1456
+ expanded.push(parentFolder);
1457
+ expanded.sort();
1458
+ this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
1459
+ }
1460
+ this.setState({ folders, expanded }, () => this.select(id));
1461
+ }), 500);
1462
+ }
1463
+ });
1464
+ }
1465
+ });
1466
+ }
1467
+ };
1468
+ reader.readAsArrayBuffer(file);
1469
+ });
1470
+ } }, ({ getRootProps, getInputProps }) => (React.createElement("div", { style: {
1471
+ ...styles.uploadDiv,
1472
+ ...(this.state.uploadFile === 'dragging' ? styles.uploadDivDragging : undefined),
1473
+ }, ...getRootProps() },
1474
+ React.createElement("input", { ...getInputProps() }),
1475
+ React.createElement(Box, { component: "div", sx: styles.uploadCenterDiv },
1476
+ React.createElement("div", { style: styles.uploadCenterTextAndIcon },
1477
+ React.createElement(UploadIcon, { style: styles.uploadCenterIcon }),
1478
+ React.createElement("div", { style: styles.uploadCenterText }, this.state.uploadFile === 'dragging'
1479
+ ? this.props.t('ra_Drop file here')
1480
+ : this.props.t('ra_Place your files here or click here to open the browse dialog'))))))),
1481
+ ];
1482
+ }
1483
+ return null;
1484
+ }
1485
+ deleteRecursive(id) {
1486
+ const item = this.findItem(id);
1487
+ if (item?.folder) {
1488
+ return (this.state.folders[id]
1489
+ ? Promise.all(this.state.folders[id].map(_item => this.deleteRecursive(_item.id)))
1490
+ : Promise.resolve()).then(() => {
1491
+ // If it is a folder of second level
1492
+ if (item.level >= 1) {
1493
+ const parts = id.split('/');
1494
+ const adapter = parts.shift();
1495
+ void this.props.socket.deleteFolder(adapter || '', parts.join('/')).then(() => {
1496
+ // remove this folder
1497
+ const folders = JSON.parse(JSON.stringify(this.state.folders));
1498
+ delete folders[item.id];
1499
+ // delete folder from parent item
1500
+ const parentId = getParentDir(item.id);
1501
+ const parentFolder = folders[parentId];
1502
+ if (parentFolder) {
1503
+ const pos = parentFolder.findIndex((f) => f.id === item.id);
1504
+ if (pos !== -1) {
1505
+ parentFolder.splice(pos, 1);
1506
+ }
1507
+ this.select(parentId, null, () => this.setState({ folders }));
1508
+ }
1509
+ });
1510
+ }
1511
+ });
1512
+ }
1513
+ const parts = id.split('/');
1514
+ const adapter = parts.shift();
1515
+ if (parts.length) {
1516
+ return this.props.socket
1517
+ .deleteFile(adapter || '', parts.join('/'))
1518
+ .catch(e => window.alert(`Cannot delete file: ${e}`));
1519
+ }
1520
+ return Promise.resolve();
1521
+ }
1522
+ deleteItem(deleteItem) {
1523
+ deleteItem = deleteItem || this.state.deleteItem;
1524
+ if (this.state.suppressDeleteConfirm) {
1525
+ this.suppressDeleteConfirm = Date.now() + 60000 * 5;
1526
+ }
1527
+ this.setState({ deleteItem: '', suppressDeleteConfirm: false }, () => this.deleteRecursive(deleteItem).then(() => {
1528
+ const newState = {};
1529
+ const pos = this.state.expanded.indexOf(deleteItem);
1530
+ if (pos !== -1) {
1531
+ const expanded = [...this.state.expanded];
1532
+ expanded.splice(pos, 1);
1533
+ this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
1534
+ newState.expanded = expanded;
1535
+ }
1536
+ if (this.state.selected === deleteItem) {
1537
+ const parts = this.state.selected.split('/');
1538
+ parts.pop();
1539
+ newState.selected = parts.join('/');
1540
+ }
1541
+ if (!this.supportSubscribes) {
1542
+ const parentFolder = this.findFirstFolder(deleteItem);
1543
+ const folders = {};
1544
+ Object.keys(this.state.folders).forEach(name => {
1545
+ if (name !== parentFolder && !name.startsWith(`${parentFolder}/`)) {
1546
+ folders[name] = this.state.folders[name];
1547
+ }
1548
+ });
1549
+ newState.folders = folders;
1550
+ this.setState(newState, () => setTimeout(() => {
1551
+ this.browseFolders([...this.state.expanded], folders)
1552
+ .then(_folders => this.setState({ folders: _folders }))
1553
+ .catch(e => console.error(e));
1554
+ }, 200));
1555
+ }
1556
+ else {
1557
+ this.setState(newState);
1558
+ }
1559
+ }));
1560
+ }
1561
+ renderDeleteDialog() {
1562
+ if (this.state.deleteItem) {
1563
+ return (React.createElement(Dialog, { key: "deleteDialog", open: !0, onClose: () => this.setState({ deleteItem: '' }), "aria-labelledby": "ar_dialog_file_delete_title" },
1564
+ React.createElement(DialogTitle, { id: "ar_dialog_file_delete_title" }, this.props.t('ra_Confirm deletion of %s', this.state.deleteItem.split('/').pop())),
1565
+ React.createElement(DialogContent, null,
1566
+ React.createElement(DialogContentText, null, this.props.t('ra_Are you sure?')),
1567
+ React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: this.state.suppressDeleteConfirm, onChange: () => {
1568
+ this.setState({ suppressDeleteConfirm: !this.state.suppressDeleteConfirm });
1569
+ } }), label: this.props.t('ra_no confirm for 5 mins') })),
1570
+ React.createElement(DialogActions, null,
1571
+ React.createElement(Button, { variant: "contained", onClick: () => this.deleteItem(''), color: "primary", autoFocus: true }, this.props.t('ra_Delete')),
1572
+ React.createElement(Button, { variant: "contained", onClick: () => this.setState({ deleteItem: '' }), color: "grey" }, this.props.t('ra_Cancel')))));
1573
+ }
1574
+ return null;
1575
+ }
1576
+ renderViewDialog() {
1577
+ const FileViewerComponent = this.props.FileViewer || FileViewer;
1578
+ return this.state.viewer ? (React.createElement(FileViewerComponent, { supportSubscribes: this.supportSubscribes || undefined, key: this.state.viewer, href: this.state.viewer, formatEditFile: this.state.formatEditFile || undefined, themeType: this.props.themeType || 'light', setStateBackgroundImage: this.setStateBackgroundImage, getStyleBackgroundImage: this.getStyleBackgroundImage, t: this.props.t, socket: this.props.socket, onClose: () => this.setState({ viewer: '', formatEditFile: '' }) })) : null;
1579
+ }
1580
+ renderError() {
1581
+ if (this.state.errorText) {
1582
+ return (React.createElement(DialogError, { key: "errorDialog", text: this.state.errorText, onClose: () => this.setState({ errorText: '' }) }));
1583
+ }
1584
+ return null;
1585
+ }
1586
+ // used in tabs/Files
1587
+ // eslint-disable-next-line react/no-unused-class-component-methods
1588
+ updateItemsAcl(info) {
1589
+ this.cacheFolders ||= JSON.parse(JSON.stringify(this.state.folders));
1590
+ let changed;
1591
+ info.forEach(it => {
1592
+ const item = this.findItem(it.id, this.cacheFolders);
1593
+ if (item && JSON.stringify(item.acl) !== JSON.stringify(it.acl)) {
1594
+ item.acl = it.acl;
1595
+ changed = true;
1596
+ }
1597
+ });
1598
+ if (changed) {
1599
+ if (this.cacheFoldersTimeout) {
1600
+ clearTimeout(this.cacheFoldersTimeout);
1601
+ }
1602
+ this.cacheFoldersTimeout = setTimeout(() => {
1603
+ this.cacheFoldersTimeout = null;
1604
+ const folders = this.cacheFolders || {};
1605
+ this.cacheFolders = null;
1606
+ this.setState({ folders });
1607
+ }, 200);
1608
+ }
1609
+ }
1610
+ changeToPath() {
1611
+ setTimeout(() => {
1612
+ if (this.state.path !== this.state.selected && (!this.lastSelect || Date.now() - this.lastSelect > 100)) {
1613
+ let folder = this.state.path;
1614
+ if (isFile(this.state.path)) {
1615
+ folder = getParentDir(this.state.path);
1616
+ }
1617
+ new Promise(resolve => {
1618
+ if (!this.state.folders[folder]) {
1619
+ this.browseFolder(folder)
1620
+ .then(folders => this.setState({ folders }, () => resolve(true)))
1621
+ .catch(err => this.setState({
1622
+ errorText: err === NOT_FOUND
1623
+ ? this.props.t('ra_Cannot find "%s"', folder)
1624
+ : this.props.t('ra_Cannot read "%s"', folder),
1625
+ }));
1626
+ }
1627
+ else {
1628
+ resolve(true);
1629
+ }
1630
+ })
1631
+ .then(result => result &&
1632
+ this.setState({ selected: this.state.path, currentDir: folder, pathFocus: false }))
1633
+ .catch(e => console.error(e));
1634
+ }
1635
+ else if (!this.lastSelect || Date.now() - this.lastSelect > 100) {
1636
+ this.setState({ pathFocus: false });
1637
+ }
1638
+ }, 100);
1639
+ }
1640
+ renderBreadcrumb() {
1641
+ const parts = this.state.currentDir.startsWith('/')
1642
+ ? this.state.currentDir.split('/')
1643
+ : `/${this.state.currentDir}`.split('/');
1644
+ const p = [];
1645
+ return (React.createElement(Breadcrumbs, { style: { paddingLeft: 8, color: '#FFF' } }, parts.map((part, i) => {
1646
+ if (part) {
1647
+ p.push(part);
1648
+ }
1649
+ const path = p.join('/');
1650
+ if (i < parts.length - 1) {
1651
+ return (React.createElement(Box, { component: "div", key: `${this.state.selected}_${i}`, sx: styles.pathDivBreadcrumbDir, onClick: e => this.changeFolder(e, path || '/') }, part || this.props.t('ra_Root')));
1652
+ }
1653
+ return (React.createElement("div", { style: styles.pathDivBreadcrumbSelected, key: `${this.state.selected}_${i}`, onClick: () => this.setState({ pathFocus: true }) }, part));
1654
+ })));
1655
+ }
1656
+ renderPath() {
1657
+ return (React.createElement(Box, { component: "div", key: "path", sx: styles.pathDiv }, this.state.pathFocus ? (React.createElement(Input, { value: this.state.path, onKeyDown: e => {
1658
+ if (e.key === 'Enter') {
1659
+ this.changeToPath();
1660
+ }
1661
+ else if (e.key === 'Escape') {
1662
+ this.setState({ pathFocus: false });
1663
+ }
1664
+ }, endAdornment: React.createElement(IconButton, { size: "small", onClick: () => this.changeToPath() },
1665
+ React.createElement(EnterIcon, null)), onBlur: () => this.changeToPath(), onChange: e => this.setState({ path: e.target.value }), style: styles.pathDivInput })) : (this.renderBreadcrumb())));
1666
+ }
1667
+ render() {
1668
+ if (!this.props.ready) {
1669
+ return React.createElement(LinearProgress, null);
1670
+ }
1671
+ if (this.state.loadAllFolders && !this.foldersLoading) {
1672
+ this.foldersLoading = true;
1673
+ setTimeout(() => {
1674
+ this.setState({ loadAllFolders: false, folders: {} }, () => {
1675
+ this.foldersLoading = false;
1676
+ this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
1677
+ });
1678
+ }, 300);
1679
+ }
1680
+ return (React.createElement("div", { style: { ...styles.root, ...this.props.style }, className: this.props.className },
1681
+ this.props.showToolbar ? this.renderToolbar() : null,
1682
+ this.state.viewType === TILE ? this.renderPath() : null,
1683
+ React.createElement("div", { style: {
1684
+ ...styles.filesDiv,
1685
+ ...styles[`filesDiv${this.state.viewType}`],
1686
+ }, onClick: e => {
1687
+ if (this.state.viewType !== TABLE) {
1688
+ if (this.state.selected !== (this.state.currentDir || '/')) {
1689
+ this.changeFolder(e, this.state.currentDir || '/');
1690
+ }
1691
+ else {
1692
+ e.stopPropagation();
1693
+ }
1694
+ }
1695
+ }, id: "dev", ref: this.refFileDiv },
1696
+ this.state.viewType === TABLE
1697
+ ? this.renderItems('/')
1698
+ : this.renderItems(this.state.currentDir || '/'),
1699
+ this.state.viewType !== TABLE ? (React.createElement("div", { style: styles.filesDivHint }, this.props.t('ra_select_folder_hint'))) : null),
1700
+ this.props.allowUpload ? this.renderInputDialog() : null,
1701
+ this.props.allowUpload ? this.renderUpload() : null,
1702
+ this.props.allowDelete ? this.renderDeleteDialog() : null,
1703
+ this.props.allowView ? this.renderViewDialog() : null,
1704
+ this.state.modalEditOfAccess && this.props.modalEditOfAccessControl
1705
+ ? this.props.modalEditOfAccessControl(this)
1706
+ : null,
1707
+ this.renderError()));
1708
+ }
1709
+ }
1710
+ export const FileBrowser = withWidth()(FileBrowserClass);
1711
+ //# sourceMappingURL=FileBrowser.js.map