@iobroker/adapter-react-v5 7.2.3 → 7.2.6

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 (375) hide show
  1. package/README.md +3 -3
  2. package/build/AdminConnection.d.ts +1 -0
  3. package/build/AdminConnection.js +2 -0
  4. package/build/AdminConnection.js.map +1 -0
  5. package/{src/Components/404.tsx → build/Components/404.js} +14 -39
  6. package/build/Components/404.js.map +1 -0
  7. package/{Components → build/Components}/ColorPicker.d.ts +2 -2
  8. package/{Components → build/Components}/ColorPicker.js +50 -65
  9. package/build/Components/ColorPicker.js.map +1 -0
  10. package/{Components → build/Components}/ComplexCron.d.ts +2 -2
  11. package/{Components → build/Components}/ComplexCron.js +43 -71
  12. package/build/Components/ComplexCron.js.map +1 -0
  13. package/{Components → build/Components}/CopyToClipboard.d.ts +1 -2
  14. package/{src/Components/CopyToClipboard.tsx → build/Components/CopyToClipboard.js} +20 -38
  15. package/build/Components/CopyToClipboard.js.map +1 -0
  16. package/{Components → build/Components}/CustomModal.d.ts +3 -3
  17. package/build/Components/CustomModal.js +60 -0
  18. package/build/Components/CustomModal.js.map +1 -0
  19. package/{Components → build/Components}/FileBrowser.d.ts +21 -21
  20. package/{Components → build/Components}/FileBrowser.js +253 -248
  21. package/build/Components/FileBrowser.js.map +1 -0
  22. package/build/Components/FileViewer.d.ts +48 -0
  23. package/build/Components/FileViewer.js +277 -0
  24. package/build/Components/FileViewer.js.map +1 -0
  25. package/{Components → build/Components}/Icon.d.ts +1 -1
  26. package/build/Components/Icon.js +140 -0
  27. package/build/Components/Icon.js.map +1 -0
  28. package/{Components → build/Components}/IconPicker.d.ts +2 -2
  29. package/build/Components/IconPicker.js +73 -0
  30. package/build/Components/IconPicker.js.map +1 -0
  31. package/{Components → build/Components}/IconSelector.d.ts +3 -3
  32. package/{Components → build/Components}/IconSelector.js +29 -57
  33. package/build/Components/IconSelector.js.map +1 -0
  34. package/{Components → build/Components}/Image.d.ts +2 -2
  35. package/{Components → build/Components}/Image.js +16 -22
  36. package/build/Components/Image.js.map +1 -0
  37. package/{Components → build/Components}/Loader.d.ts +2 -2
  38. package/{Components → build/Components}/Loader.js +15 -40
  39. package/build/Components/Loader.js.map +1 -0
  40. package/{Components → build/Components}/Loaders/MV.d.ts +2 -2
  41. package/build/Components/Loaders/MV.js +61 -0
  42. package/build/Components/Loaders/MV.js.map +1 -0
  43. package/{Components → build/Components}/Loaders/PT.d.ts +2 -2
  44. package/{Components → build/Components}/Loaders/PT.js +10 -35
  45. package/build/Components/Loaders/PT.js.map +1 -0
  46. package/{Components → build/Components}/Loaders/Vendor.d.ts +2 -2
  47. package/build/Components/Loaders/Vendor.js +52 -0
  48. package/build/Components/Loaders/Vendor.js.map +1 -0
  49. package/{Components → build/Components}/Logo.d.ts +2 -2
  50. package/build/Components/Logo.js +108 -0
  51. package/build/Components/Logo.js.map +1 -0
  52. package/{Components → build/Components}/MDUtils.d.ts +1 -2
  53. package/{Components → build/Components}/MDUtils.js +4 -9
  54. package/build/Components/MDUtils.js.map +1 -0
  55. package/{Components → build/Components}/ObjectBrowser.d.ts +7 -4
  56. package/{Components → build/Components}/ObjectBrowser.js +1183 -1133
  57. package/build/Components/ObjectBrowser.js.map +1 -0
  58. package/{Components → build/Components}/Router.d.ts +1 -2
  59. package/{Components → build/Components}/Router.js +6 -7
  60. package/build/Components/Router.js.map +1 -0
  61. package/{Components → build/Components}/SaveCloseButtons.d.ts +2 -2
  62. package/build/Components/SaveCloseButtons.js +65 -0
  63. package/build/Components/SaveCloseButtons.js.map +1 -0
  64. package/{Components → build/Components}/Schedule.d.ts +3 -3
  65. package/{Components → build/Components}/Schedule.js +246 -261
  66. package/build/Components/Schedule.js.map +1 -0
  67. package/{Components → build/Components}/SelectWithIcon.d.ts +2 -2
  68. package/build/Components/SelectWithIcon.js +135 -0
  69. package/build/Components/SelectWithIcon.js.map +1 -0
  70. package/build/Components/SimpleCron/cronText.js +15 -0
  71. package/build/Components/SimpleCron/cronText.js.map +1 -0
  72. package/{Components → build/Components}/SimpleCron/index.d.ts +2 -2
  73. package/{Components → build/Components}/SimpleCron/index.js +55 -56
  74. package/build/Components/SimpleCron/index.js.map +1 -0
  75. package/{Components → build/Components}/TabContainer.d.ts +2 -2
  76. package/build/Components/TabContainer.js +23 -0
  77. package/build/Components/TabContainer.js.map +1 -0
  78. package/{Components → build/Components}/TabContent.d.ts +3 -2
  79. package/build/Components/TabContent.js +20 -0
  80. package/build/Components/TabContent.js.map +1 -0
  81. package/build/Components/TabHeader.d.ts +6 -0
  82. package/build/Components/TabHeader.js +6 -0
  83. package/build/Components/TabHeader.js.map +1 -0
  84. package/{Components → build/Components}/TableResize.d.ts +2 -2
  85. package/{src/Components/TableResize.tsx → build/Components/TableResize.js} +64 -134
  86. package/build/Components/TableResize.js.map +1 -0
  87. package/{Components → build/Components}/TextWithIcon.d.ts +2 -2
  88. package/{src/Components/TextWithIcon.tsx → build/Components/TextWithIcon.js} +30 -75
  89. package/build/Components/TextWithIcon.js.map +1 -0
  90. package/{Components → build/Components}/ToggleThemeMenu.d.ts +1 -1
  91. package/build/Components/ToggleThemeMenu.js +13 -0
  92. package/build/Components/ToggleThemeMenu.js.map +1 -0
  93. package/{Components → build/Components}/TreeTable.d.ts +3 -3
  94. package/{Components → build/Components}/TreeTable.js +87 -99
  95. package/build/Components/TreeTable.js.map +1 -0
  96. package/{Components → build/Components}/UploadImage.d.ts +2 -2
  97. package/{Components → build/Components}/UploadImage.js +46 -69
  98. package/build/Components/UploadImage.js.map +1 -0
  99. package/{Components → build/Components}/Utils.d.ts +2 -2
  100. package/{Components → build/Components}/Utils.js +47 -60
  101. package/build/Components/Utils.js.map +1 -0
  102. package/build/Components/withWidth.d.ts +2 -0
  103. package/build/Components/withWidth.js +22 -0
  104. package/build/Components/withWidth.js.map +1 -0
  105. package/build/Connection.d.ts +1 -0
  106. package/build/Connection.js +2 -0
  107. package/build/Connection.js.map +1 -0
  108. package/{Dialogs → build/Dialogs}/ComplexCron.d.ts +2 -2
  109. package/build/Dialogs/ComplexCron.js +85 -0
  110. package/build/Dialogs/ComplexCron.js.map +1 -0
  111. package/{Dialogs → build/Dialogs}/Confirm.d.ts +2 -2
  112. package/build/Dialogs/Confirm.js +83 -0
  113. package/build/Dialogs/Confirm.js.map +1 -0
  114. package/{Dialogs → build/Dialogs}/Cron.d.ts +2 -2
  115. package/build/Dialogs/Cron.js +72 -0
  116. package/build/Dialogs/Cron.js.map +1 -0
  117. package/{Dialogs → build/Dialogs}/Error.d.ts +2 -2
  118. package/build/Dialogs/Error.js +27 -0
  119. package/build/Dialogs/Error.js.map +1 -0
  120. package/{Dialogs → build/Dialogs}/Message.d.ts +2 -2
  121. package/build/Dialogs/Message.js +29 -0
  122. package/build/Dialogs/Message.js.map +1 -0
  123. package/{Dialogs → build/Dialogs}/SelectFile.d.ts +2 -2
  124. package/build/Dialogs/SelectFile.js +116 -0
  125. package/build/Dialogs/SelectFile.js.map +1 -0
  126. package/{Dialogs → build/Dialogs}/SelectID.d.ts +3 -3
  127. package/{Dialogs → build/Dialogs}/SelectID.js +28 -53
  128. package/build/Dialogs/SelectID.js.map +1 -0
  129. package/{Dialogs → build/Dialogs}/SimpleCron.d.ts +2 -2
  130. package/build/Dialogs/SimpleCron.js +46 -0
  131. package/build/Dialogs/SimpleCron.js.map +1 -0
  132. package/build/Dialogs/TextInput.d.ts +2 -0
  133. package/build/Dialogs/TextInput.js +31 -0
  134. package/build/Dialogs/TextInput.js.map +1 -0
  135. package/{GenericApp.d.ts → build/GenericApp.d.ts} +2 -3
  136. package/{GenericApp.js → build/GenericApp.js} +162 -176
  137. package/build/GenericApp.js.map +1 -0
  138. package/{LegacyConnection.d.ts → build/LegacyConnection.d.ts} +69 -4
  139. package/{LegacyConnection.js → build/LegacyConnection.js} +106 -99
  140. package/build/LegacyConnection.js.map +1 -0
  141. package/{Prompt.d.ts → build/Prompt.d.ts} +1 -1
  142. package/{Prompt.js → build/Prompt.js} +3 -4
  143. package/build/Prompt.js.map +1 -0
  144. package/build/Theme.d.ts +5 -0
  145. package/{Theme.js → build/Theme.js} +37 -32
  146. package/build/Theme.js.map +1 -0
  147. package/build/assets/devices/parseNames.d.ts +0 -0
  148. package/build/assets/devices/parseNames.js +35 -0
  149. package/build/assets/devices/parseNames.js.map +1 -0
  150. package/build/assets/rooms/parseNames.d.ts +0 -0
  151. package/build/assets/rooms/parseNames.js +35 -0
  152. package/build/assets/rooms/parseNames.js.map +1 -0
  153. package/build/dictionary.d.ts +1 -0
  154. package/build/dictionary.js +25 -0
  155. package/build/dictionary.js.map +1 -0
  156. package/build/i18n/de.json +449 -0
  157. package/build/i18n/en.json +449 -0
  158. package/build/i18n/es.json +449 -0
  159. package/build/i18n/fr.json +449 -0
  160. package/build/i18n/it.json +449 -0
  161. package/build/i18n/nl.json +449 -0
  162. package/build/i18n/pl.json +449 -0
  163. package/build/i18n/pt.json +449 -0
  164. package/build/i18n/ru.json +449 -0
  165. package/build/i18n/uk.json +449 -0
  166. package/build/i18n/zh-cn.json +449 -0
  167. package/{i18n.d.ts → build/i18n.d.ts} +2 -2
  168. package/{i18n.js → build/i18n.js} +9 -11
  169. package/build/i18n.js.map +1 -0
  170. package/build/icons/IconAdapter.d.ts +3 -0
  171. package/build/icons/IconAdapter.js +6 -0
  172. package/build/icons/IconAdapter.js.map +1 -0
  173. package/build/icons/IconAlias.d.ts +3 -0
  174. package/build/icons/IconAlias.js +6 -0
  175. package/build/icons/IconAlias.js.map +1 -0
  176. package/build/icons/IconChannel.d.ts +3 -0
  177. package/build/icons/IconChannel.js +9 -0
  178. package/build/icons/IconChannel.js.map +1 -0
  179. package/build/icons/IconClearFilter.d.ts +3 -0
  180. package/build/icons/IconClearFilter.js +7 -0
  181. package/build/icons/IconClearFilter.js.map +1 -0
  182. package/build/icons/IconClosed.d.ts +3 -0
  183. package/build/icons/IconClosed.js +6 -0
  184. package/build/icons/IconClosed.js.map +1 -0
  185. package/build/icons/IconCopy.d.ts +3 -0
  186. package/build/icons/IconCopy.js +5 -0
  187. package/build/icons/IconCopy.js.map +1 -0
  188. package/build/icons/IconDevice.d.ts +3 -0
  189. package/build/icons/IconDevice.js +15 -0
  190. package/build/icons/IconDevice.js.map +1 -0
  191. package/build/icons/IconDocument.d.ts +3 -0
  192. package/build/icons/IconDocument.js +6 -0
  193. package/build/icons/IconDocument.js.map +1 -0
  194. package/build/icons/IconDocumentReadOnly.d.ts +3 -0
  195. package/build/icons/IconDocumentReadOnly.js +7 -0
  196. package/build/icons/IconDocumentReadOnly.js.map +1 -0
  197. package/build/icons/IconExpert.d.ts +3 -0
  198. package/build/icons/IconExpert.js +6 -0
  199. package/build/icons/IconExpert.js.map +1 -0
  200. package/build/icons/IconFx.d.ts +3 -0
  201. package/build/icons/IconFx.js +5 -0
  202. package/build/icons/IconFx.js.map +1 -0
  203. package/build/icons/IconInstance.d.ts +3 -0
  204. package/build/icons/IconInstance.js +6 -0
  205. package/build/icons/IconInstance.js.map +1 -0
  206. package/build/icons/IconLogout.d.ts +3 -0
  207. package/build/icons/IconLogout.js +6 -0
  208. package/build/icons/IconLogout.js.map +1 -0
  209. package/build/icons/IconNoIcon.d.ts +3 -0
  210. package/build/icons/IconNoIcon.js +5 -0
  211. package/build/icons/IconNoIcon.js.map +1 -0
  212. package/build/icons/IconOpen.d.ts +3 -0
  213. package/build/icons/IconOpen.js +6 -0
  214. package/build/icons/IconOpen.js.map +1 -0
  215. package/{icons → build/icons}/IconProps.d.ts +1 -1
  216. package/build/icons/IconProps.js +2 -0
  217. package/build/icons/IconProps.js.map +1 -0
  218. package/build/icons/IconState.d.ts +3 -0
  219. package/build/icons/IconState.js +6 -0
  220. package/build/icons/IconState.js.map +1 -0
  221. package/build/index.d.ts +67 -0
  222. package/build/index.js +67 -0
  223. package/build/index.js.map +1 -0
  224. package/{types.d.ts → build/types.d.ts} +1 -1
  225. package/package.json +84 -47
  226. package/AdminConnection.d.ts +0 -2
  227. package/AdminConnection.js +0 -4
  228. package/Components/404.js +0 -101
  229. package/Components/CopyToClipboard.js +0 -163
  230. package/Components/CustomModal.js +0 -88
  231. package/Components/FileViewer.d.ts +0 -10
  232. package/Components/FileViewer.js +0 -305
  233. package/Components/Icon.js +0 -148
  234. package/Components/IconPicker.js +0 -98
  235. package/Components/Loaders/MV.js +0 -66
  236. package/Components/Loaders/Vendor.js +0 -77
  237. package/Components/Logo.js +0 -117
  238. package/Components/SaveCloseButtons.js +0 -69
  239. package/Components/SelectWithIcon.js +0 -168
  240. package/Components/SimpleCron/cronText.js +0 -19
  241. package/Components/TabContainer.js +0 -25
  242. package/Components/TabContent.js +0 -21
  243. package/Components/TabHeader.d.ts +0 -6
  244. package/Components/TabHeader.js +0 -11
  245. package/Components/TableResize.js +0 -226
  246. package/Components/TextWithIcon.js +0 -119
  247. package/Components/ToggleThemeMenu.js +0 -18
  248. package/Components/withWidth.d.ts +0 -3
  249. package/Components/withWidth.js +0 -27
  250. package/Connection.d.ts +0 -3
  251. package/Connection.js +0 -8
  252. package/Dialogs/ComplexCron.js +0 -90
  253. package/Dialogs/Confirm.js +0 -111
  254. package/Dialogs/Cron.js +0 -100
  255. package/Dialogs/Error.js +0 -55
  256. package/Dialogs/Message.js +0 -57
  257. package/Dialogs/SelectFile.js +0 -119
  258. package/Dialogs/SimpleCron.js +0 -51
  259. package/Dialogs/TextInput.d.ts +0 -3
  260. package/Dialogs/TextInput.js +0 -35
  261. package/Theme.d.ts +0 -6
  262. package/i18n/de.json +0 -449
  263. package/i18n/en.json +0 -449
  264. package/i18n/es.json +0 -449
  265. package/i18n/fr.json +0 -449
  266. package/i18n/it.json +0 -449
  267. package/i18n/nl.json +0 -449
  268. package/i18n/pl.json +0 -449
  269. package/i18n/pt.json +0 -449
  270. package/i18n/ru.json +0 -449
  271. package/i18n/uk.json +0 -449
  272. package/i18n/zh-cn.json +0 -449
  273. package/icons/IconAdapter.d.ts +0 -4
  274. package/icons/IconAdapter.js +0 -10
  275. package/icons/IconAlias.d.ts +0 -4
  276. package/icons/IconAlias.js +0 -10
  277. package/icons/IconChannel.d.ts +0 -4
  278. package/icons/IconChannel.js +0 -13
  279. package/icons/IconClearFilter.d.ts +0 -4
  280. package/icons/IconClearFilter.js +0 -11
  281. package/icons/IconClosed.d.ts +0 -4
  282. package/icons/IconClosed.js +0 -10
  283. package/icons/IconCopy.d.ts +0 -4
  284. package/icons/IconCopy.js +0 -9
  285. package/icons/IconDevice.d.ts +0 -4
  286. package/icons/IconDevice.js +0 -19
  287. package/icons/IconDocument.d.ts +0 -4
  288. package/icons/IconDocument.js +0 -10
  289. package/icons/IconDocumentReadOnly.d.ts +0 -4
  290. package/icons/IconDocumentReadOnly.js +0 -11
  291. package/icons/IconExpert.d.ts +0 -4
  292. package/icons/IconExpert.js +0 -10
  293. package/icons/IconFx.d.ts +0 -4
  294. package/icons/IconFx.js +0 -9
  295. package/icons/IconInstance.d.ts +0 -4
  296. package/icons/IconInstance.js +0 -10
  297. package/icons/IconLogout.d.ts +0 -4
  298. package/icons/IconLogout.js +0 -10
  299. package/icons/IconNoIcon.d.ts +0 -4
  300. package/icons/IconNoIcon.js +0 -9
  301. package/icons/IconOpen.d.ts +0 -4
  302. package/icons/IconOpen.js +0 -10
  303. package/icons/IconProps.js +0 -2
  304. package/icons/IconState.d.ts +0 -4
  305. package/icons/IconState.js +0 -10
  306. package/index.d.ts +0 -128
  307. package/index.js +0 -215
  308. package/src/AdminConnection.tsx +0 -3
  309. package/src/Components/ColorPicker.tsx +0 -343
  310. package/src/Components/ComplexCron.tsx +0 -561
  311. package/src/Components/CustomModal.tsx +0 -170
  312. package/src/Components/FileBrowser.tsx +0 -2550
  313. package/src/Components/FileViewer.tsx +0 -412
  314. package/src/Components/Icon.tsx +0 -238
  315. package/src/Components/IconPicker.tsx +0 -165
  316. package/src/Components/IconSelector.tsx +0 -2220
  317. package/src/Components/Image.tsx +0 -193
  318. package/src/Components/Loader.tsx +0 -328
  319. package/src/Components/Logo.tsx +0 -176
  320. package/src/Components/MDUtils.tsx +0 -104
  321. package/src/Components/ObjectBrowser.tsx +0 -8935
  322. package/src/Components/Router.tsx +0 -90
  323. package/src/Components/SaveCloseButtons.tsx +0 -117
  324. package/src/Components/Schedule.tsx +0 -1995
  325. package/src/Components/SelectWithIcon.tsx +0 -239
  326. package/src/Components/TabContainer.tsx +0 -57
  327. package/src/Components/TabContent.tsx +0 -38
  328. package/src/Components/TabHeader.tsx +0 -20
  329. package/src/Components/ToggleThemeMenu.tsx +0 -52
  330. package/src/Components/TreeTable.tsx +0 -1002
  331. package/src/Components/UploadImage.tsx +0 -643
  332. package/src/Components/Utils.tsx +0 -1802
  333. package/src/Components/loader.css +0 -231
  334. package/src/Components/withWidth.tsx +0 -32
  335. package/src/Connection.tsx +0 -5
  336. package/src/Dialogs/ComplexCron.tsx +0 -163
  337. package/src/Dialogs/Confirm.tsx +0 -185
  338. package/src/Dialogs/Cron.tsx +0 -192
  339. package/src/Dialogs/Error.tsx +0 -67
  340. package/src/Dialogs/Message.tsx +0 -73
  341. package/src/Dialogs/SelectFile.tsx +0 -280
  342. package/src/Dialogs/SelectID.tsx +0 -310
  343. package/src/Dialogs/SimpleCron.tsx +0 -101
  344. package/src/Dialogs/TextInput.tsx +0 -99
  345. package/src/GenericApp.tsx +0 -1076
  346. package/src/LegacyConnection.tsx +0 -3720
  347. package/src/Prompt.tsx +0 -22
  348. package/src/Theme.tsx +0 -472
  349. package/src/icons/IconAdapter.tsx +0 -22
  350. package/src/icons/IconAlias.tsx +0 -22
  351. package/src/icons/IconChannel.tsx +0 -60
  352. package/src/icons/IconClearFilter.tsx +0 -24
  353. package/src/icons/IconClosed.tsx +0 -22
  354. package/src/icons/IconCopy.tsx +0 -21
  355. package/src/icons/IconDevice.tsx +0 -126
  356. package/src/icons/IconDocument.tsx +0 -22
  357. package/src/icons/IconDocumentReadOnly.tsx +0 -27
  358. package/src/icons/IconExpert.tsx +0 -26
  359. package/src/icons/IconFx.tsx +0 -38
  360. package/src/icons/IconInstance.tsx +0 -22
  361. package/src/icons/IconLogout.tsx +0 -32
  362. package/src/icons/IconNoIcon.tsx +0 -21
  363. package/src/icons/IconOpen.tsx +0 -22
  364. package/src/icons/IconProps.tsx +0 -16
  365. package/src/icons/IconState.tsx +0 -38
  366. package/src/index.css +0 -56
  367. /package/{Components → build/Components}/404.d.ts +0 -0
  368. /package/{Components → build/Components}/SimpleCron/cronText.d.ts +0 -0
  369. /package/{assets → build/assets}/devices.json +0 -0
  370. /package/{assets → build/assets}/lamp_ceiling.svg +0 -0
  371. /package/{assets → build/assets}/lamp_table.svg +0 -0
  372. /package/{assets → build/assets}/no_icon.svg +0 -0
  373. /package/{assets → build/assets}/rooms.json +0 -0
  374. /package/{index.css → build/index.css} +0 -0
  375. /package/{tasks.js → tasksExample.js} +0 -0
@@ -1,2550 +0,0 @@
1
- /**
2
- * Copyright 2020-2024, Denis Haev <dogafox@gmail.com>
3
- *
4
- * MIT License
5
- *
6
- */
7
- import React, { Component } from 'react';
8
- import Dropzone from 'react-dropzone';
9
-
10
- import {
11
- LinearProgress,
12
- ListItemIcon,
13
- ListItemText,
14
- Menu,
15
- MenuItem,
16
- Tooltip,
17
- CircularProgress,
18
- Toolbar,
19
- IconButton,
20
- Fab,
21
- Dialog,
22
- DialogTitle,
23
- DialogContent,
24
- DialogContentText,
25
- DialogActions,
26
- Button,
27
- Input,
28
- Breadcrumbs,
29
- Box,
30
- } from '@mui/material';
31
-
32
- // MUI Icons
33
- import {
34
- Refresh as RefreshIcon,
35
- Close as CloseIcon,
36
- Bookmark as JsonIcon,
37
- BookmarkBorder as CssIcon,
38
- Description as HtmlIcon,
39
- Edit as EditIcon,
40
- Code as JSIcon,
41
- InsertDriveFile as FileIcon,
42
- Publish as UploadIcon,
43
- MusicNote as MusicIcon,
44
- SaveAlt as DownloadIcon,
45
- CreateNewFolder as AddFolderIcon,
46
- FolderOpen as EmptyFilterIcon,
47
- List as IconList,
48
- ViewModule as IconTile,
49
- ArrowBack as IconBack,
50
- Delete as DeleteIcon,
51
- Brightness6 as Brightness5Icon,
52
- Image as TypeIconImages,
53
- FontDownload as TypeIconTxt,
54
- AudioFile as TypeIconAudio,
55
- Videocam as TypeIconVideo,
56
- KeyboardReturn as EnterIcon,
57
- FolderSpecial as RestrictedIcon,
58
- } from '@mui/icons-material';
59
-
60
- import type { Connection } from '@iobroker/socket-client';
61
-
62
- import ErrorDialog from '../Dialogs/Error';
63
- import Utils from './Utils';
64
- import TextInputDialog from '../Dialogs/TextInput';
65
-
66
- // Custom Icons
67
- import IconExpert from '../icons/IconExpert';
68
- import IconClosed from '../icons/IconClosed';
69
- import IconOpen from '../icons/IconOpen';
70
- import IconNoIcon from '../icons/IconNoIcon';
71
- import Icon from './Icon';
72
-
73
- import withWidth from './withWidth';
74
- import type { ThemeName, ThemeType, Translate, IobTheme } from '../types';
75
-
76
- import FileViewer, { EXTENSIONS } from './FileViewer';
77
-
78
- const ROW_HEIGHT = 32;
79
- const BUTTON_WIDTH = 32;
80
- const TILE_HEIGHT = 120;
81
- const TILE_WIDTH = 64;
82
-
83
- const NOT_FOUND = 'Not found';
84
-
85
- // Todo: replace with js-controller types
86
- export interface MetaACL extends ioBroker.ObjectACL {
87
- file: number;
88
- }
89
-
90
- // Todo: replace with js-controller types
91
- export interface MetaObject extends ioBroker.MetaObject {
92
- acl: MetaACL;
93
- }
94
-
95
- const FILE_TYPE_ICONS: Record<string, React.FC<{ fontSize?: 'small' }>> = {
96
- all: FileIcon,
97
- images: TypeIconImages,
98
- code: JSIcon,
99
- txt: TypeIconTxt,
100
- audio: TypeIconAudio,
101
- video: TypeIconVideo,
102
- };
103
-
104
- const styles: Record<string, any> = {
105
- dialog: (theme: IobTheme) => ({
106
- height: `calc(100% - ${theme.mixins.toolbar.minHeight}px)`,
107
- }),
108
- root: {
109
- width: '100%',
110
- overflow: 'hidden',
111
- height: '100%',
112
- position: 'relative',
113
- },
114
- filesDiv: {
115
- width: 'calc(100% - 16px)',
116
- overflowX: 'hidden',
117
- overflowY: 'auto',
118
- padding: 8,
119
- },
120
- filesDivHint: {
121
- position: 'absolute',
122
- bottom: 0,
123
- left: 20,
124
- opacity: 0.7,
125
- fontStyle: 'italic',
126
- fontSize: 12,
127
- },
128
- filesDivTable: {
129
- height: 'calc(100% - 56px)',
130
- },
131
- filesDivTile: {
132
- height: `calc(100% - ${48 * 2 + 8}px)`,
133
- display: 'flex',
134
- alignContent: 'flex-start',
135
- alignItems: 'stretch',
136
- flexWrap: 'wrap',
137
- flex: `0 0 ${TILE_WIDTH}px`,
138
- },
139
-
140
- itemTile: (theme: IobTheme) => ({
141
- position: 'relative',
142
- userSelect: 'none',
143
- cursor: 'pointer',
144
- height: TILE_HEIGHT,
145
- width: TILE_WIDTH,
146
- display: 'inline-block',
147
- textAlign: 'center',
148
- opacity: 0.1,
149
- transition: 'opacity 1s',
150
- margin: 4,
151
- '&:hover': {
152
- background: theme.palette.secondary.light,
153
- color: Utils.invertColor(theme.palette.secondary.main, true),
154
- },
155
- }),
156
- itemNameFolderTile: {
157
- fontWeight: 'bold',
158
- },
159
- itemNameTile: {
160
- width: '100%',
161
- height: 32,
162
- overflow: 'hidden',
163
- textOverflow: 'ellipsis',
164
- fontSize: 12,
165
- textAlign: 'center',
166
- wordBreak: 'break-all',
167
- },
168
- itemFolderIconTile: (theme: IobTheme) => ({
169
- width: '100%',
170
- height: TILE_HEIGHT - 32 - 16 - 8, // name + size
171
- display: 'block',
172
- pl: 1,
173
- color: theme.palette.secondary.main || '#fbff7d',
174
- }),
175
- itemFolderIconBack: (theme: IobTheme) => ({
176
- position: 'absolute',
177
- top: 22,
178
- left: 18,
179
- zIndex: 1,
180
- color: theme.palette.mode === 'dark' ? '#FFF' : '#000',
181
- }),
182
- itemSizeTile: {
183
- width: '100%',
184
- height: 16,
185
- textAlign: 'center',
186
- fontSize: 10,
187
- },
188
- itemImageTile: {
189
- width: 'calc(100% - 8px)',
190
- height: TILE_HEIGHT - 32 - 16 - 8, // name + size
191
- margin: 4,
192
- display: 'block',
193
- textAlign: 'center',
194
- objectFit: 'contain',
195
- },
196
- itemIconTile: {
197
- width: '100%',
198
- height: TILE_HEIGHT - 32 - 16 - 8, // name + size
199
- display: 'block',
200
- objectFit: 'contain',
201
- },
202
-
203
- itemSelected: (theme: IobTheme) => ({
204
- background: theme.palette.primary.main,
205
- color: Utils.invertColor(theme.palette.primary.main, true),
206
- }),
207
-
208
- itemTable: (theme: IobTheme) => ({
209
- userSelect: 'none',
210
- cursor: 'pointer',
211
- height: ROW_HEIGHT,
212
- display: 'inline-flex',
213
- lineHeight: `${ROW_HEIGHT}px`,
214
- '&:hover': {
215
- background: theme.palette.secondary.light,
216
- color: Utils.invertColor(theme.palette.secondary.main, true),
217
- },
218
- }),
219
- itemNameTable: {
220
- display: 'inline-block',
221
- pl: '10px',
222
- fontSize: '1rem',
223
- verticalAlign: 'top',
224
- flexGrow: 1,
225
- textOverflow: 'ellipsis',
226
- whiteSpace: 'nowrap',
227
- overflow: 'hidden',
228
- '@media screen and (max-width: 500px)': {
229
- textAlign: 'end',
230
- direction: 'rtl',
231
- },
232
- },
233
- itemNameFolderTable: {
234
- fontWeight: 'bold',
235
- },
236
- itemSizeTable: {
237
- display: 'inline-block',
238
- width: 60,
239
- verticalAlign: 'top',
240
- textAlign: 'right',
241
- whiteSpace: 'nowrap',
242
- },
243
- itemAccessTable: {
244
- // display: 'inline-block',
245
- verticalAlign: 'top',
246
- width: 60,
247
- textAlign: 'right',
248
- paddingRight: 5,
249
- display: 'flex',
250
- justifyContent: 'center',
251
- },
252
- itemImageTable: {
253
- display: 'inline-block',
254
- width: 30,
255
- marginTop: 1,
256
- objectFit: 'contain',
257
- maxHeight: 30,
258
- },
259
- itemNoImageTable: {
260
- marginTop: 6,
261
- },
262
- itemIconTable: {
263
- display: 'inline-block',
264
- marginTop: 1,
265
- width: 30,
266
- height: 30,
267
- },
268
- itemFolderTable: {},
269
- itemFolderTemp: {
270
- opacity: 0.4,
271
- },
272
- itemFolderIconTable: (theme: IobTheme) => ({
273
- marginTop: 1,
274
- marginLeft: 8,
275
- display: 'inline-block',
276
- width: 30,
277
- height: 30,
278
- color: theme.palette.secondary.main || '#fbff7d',
279
- }),
280
- itemDownloadButtonTable: (theme: IobTheme) => ({
281
- display: 'inline-block',
282
- width: BUTTON_WIDTH,
283
- height: ROW_HEIGHT,
284
- minWidth: BUTTON_WIDTH,
285
- verticalAlign: 'middle',
286
- textAlign: 'center',
287
- padding: 0,
288
- borderRadius: `${BUTTON_WIDTH / 2}px`,
289
- '&:hover': {
290
- backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
291
- },
292
- '& span': {
293
- pt: '9px',
294
- },
295
- '& svg': {
296
- width: 14,
297
- height: 14,
298
- fontSize: '1rem',
299
- mt: '-3px',
300
- verticalAlign: 'middle',
301
- color: theme.palette.mode === 'dark' ? '#EEE' : '#111',
302
- },
303
- }),
304
- itemDownloadEmptyTable: {
305
- display: 'inline-block',
306
- width: BUTTON_WIDTH,
307
- height: ROW_HEIGHT,
308
- minWidth: BUTTON_WIDTH,
309
- padding: 0,
310
- },
311
- itemAclButtonTable: {
312
- width: BUTTON_WIDTH,
313
- height: ROW_HEIGHT,
314
- minWidth: BUTTON_WIDTH,
315
- verticalAlign: 'top',
316
- padding: 0,
317
- fontSize: 12,
318
- display: 'flex',
319
- },
320
- itemDeleteButtonTable: {
321
- display: 'inline-block',
322
- width: BUTTON_WIDTH,
323
- height: ROW_HEIGHT,
324
- minWidth: BUTTON_WIDTH,
325
- verticalAlign: 'top',
326
- padding: 0,
327
- '& svg': {
328
- width: 18,
329
- height: 18,
330
- fontSize: '1.5rem',
331
- },
332
- },
333
-
334
- uploadDiv: {
335
- top: 0,
336
- zIndex: 1,
337
- bottom: 0,
338
- left: 0,
339
- right: 0,
340
- position: 'absolute',
341
- opacity: 0.9,
342
- textAlign: 'center',
343
- background: '#FFFFFF',
344
- },
345
- uploadDivDragging: {
346
- opacity: 1,
347
- },
348
-
349
- uploadCenterDiv: (theme: IobTheme) => ({
350
- m: '20px',
351
- border: '3px dashed grey',
352
- borderRadius: '30px',
353
- width: 'calc(100% - 40px)',
354
- height: 'calc(100% - 40px)',
355
- position: 'relative',
356
- color: theme.palette.mode === 'dark' ? '#222' : '#CCC',
357
- display: 'flex',
358
- alignItems: 'center',
359
- justifyContent: 'center',
360
- }),
361
- uploadCenterIcon: {
362
- width: '25%',
363
- height: '25%',
364
- },
365
- uploadCenterText: {
366
- fontSize: 24,
367
- fontWeight: 'bold',
368
- },
369
- uploadCloseButton: {
370
- zIndex: 2,
371
- position: 'absolute',
372
- top: 30,
373
- right: 30,
374
- },
375
- uploadCenterTextAndIcon: {
376
- position: 'absolute',
377
- height: '30%',
378
- width: '100%',
379
- margin: 'auto',
380
- opacity: 0.3,
381
- },
382
- menuButtonExpertActive: {
383
- color: '#c00000',
384
- },
385
- menuButtonRestrictActive: {
386
- color: '#c05000',
387
- },
388
- pathDiv: (theme: IobTheme) => ({
389
- display: 'flex',
390
- width: 'calc(100% - 16px)',
391
- ml: 1,
392
- mr: 1,
393
- textOverflow: 'clip',
394
- overflow: 'hidden',
395
- whiteSpace: 'nowrap',
396
- backgroundColor: theme.palette.secondary.main,
397
- }),
398
- pathDivInput: {
399
- width: '100%',
400
- },
401
- pathDivBreadcrumbDir: (theme: IobTheme) => ({
402
- pl: '2px',
403
- pr: '2px',
404
- cursor: 'pointer',
405
- '&:hover': {
406
- background: theme.palette.primary.main,
407
- },
408
- }),
409
- backgroundImageLight: {
410
- background: 'white',
411
- },
412
- backgroundImageDark: {
413
- background: 'black',
414
- },
415
- backgroundImageColored: {
416
- background: 'silver',
417
- },
418
- specialFolder: (theme: IobTheme) => ({
419
- color: theme.palette.mode === 'dark' ? '#229b0f' : '#5dd300',
420
- }),
421
- tooltip: {
422
- pointerEvents: 'none',
423
- },
424
- };
425
-
426
- const USER_DATA = '0_userdata.0';
427
-
428
- function getParentDir(dir: string | null): string {
429
- const parts = (dir || '').split('/');
430
- if (parts.length) {
431
- parts.pop();
432
- }
433
- return parts.join('/');
434
- }
435
-
436
- function isFile(path: string): boolean {
437
- const ext = Utils.getFileExtension(path);
438
- return !!(ext?.toLowerCase().match(/[a-z]+/) && ext.length < 5);
439
- }
440
-
441
- const TABLE = 'Table';
442
- const TILE = 'Tile';
443
-
444
- export interface FileBrowserProps {
445
- /** The key to identify this component. */
446
- key?: string;
447
- /** Additional styling for this component. */
448
- style?: React.CSSProperties;
449
- /** The CSS class name. */
450
- className?: string;
451
- /** Translation function. */
452
- t: Translate;
453
- /** The selected language. */
454
- lang: ioBroker.Languages;
455
- /** The socket connection. */
456
- socket: Connection;
457
- /** Is the component data ready. */
458
- ready?: boolean;
459
- /** Is expert mode enabled? (default: false) */
460
- expertMode?: boolean;
461
- /** Show the toolbar? (default: false) */
462
- showToolbar?: boolean;
463
- /** If defined, allow selecting only files from this folder and subfolders */
464
- limitPath?: string;
465
- /** Allow upload of new files? (default: false) */
466
- allowUpload?: boolean;
467
- /** Allow download of files? (default: false) */
468
- allowDownload?: boolean;
469
- /** Allow creation of new folders? (default: false) */
470
- allowCreateFolder?: boolean;
471
- /** Allow deleting files? (default: false) */
472
- allowDelete?: boolean;
473
- /** Allow viewing files? (default: false) */
474
- allowView?: boolean;
475
- /** Prefix (default: '.') */
476
- imagePrefix?: string;
477
- /** Show the expert button? */
478
- showExpertButton?: boolean;
479
- /** Type of view */
480
- viewType?: 'Table' | 'Tile';
481
- /** Show the buttons to switch the view from table to tile? (default: false) */
482
- showViewTypeButton?: boolean;
483
- /** The ID of the selected file. */
484
- selected?: string | string[];
485
- /** The file extensions to show, like ['png', 'svg', 'bmp', 'jpg', 'jpeg', 'gif']. */
486
- filterFiles?: string[];
487
- /** The file extension categories to show. */
488
- filterByType?: 'images' | 'code' | 'txt';
489
- /** Callback for file selection. */
490
- onSelect?: (id: string | string[], isDoubleClick?: boolean, isFolder?: boolean) => void;
491
- /** Theme name */
492
- themeName?: ThemeName;
493
- /** Theme type. */
494
- themeType?: ThemeType;
495
- /** Theme object. */
496
- theme: IobTheme;
497
-
498
- /** Padding in pixels for folder levels */
499
- levelPadding?: number;
500
-
501
- restrictToFolder?: string;
502
-
503
- modalEditOfAccessControl?: (obj: FileBrowserClass) => React.JSX.Element | null;
504
-
505
- allowNonRestricted?: boolean;
506
-
507
- showTypeSelector?: boolean;
508
- }
509
-
510
- export interface FolderOrFileItem {
511
- id: string;
512
- level: number;
513
- name: string;
514
- folder: boolean;
515
- temp?: boolean;
516
-
517
- size?: number | undefined;
518
- ext?: string | null;
519
- modified?: number;
520
- title?: ioBroker.StringOrTranslated;
521
- meta?: boolean;
522
- from?: string;
523
- ts?: number;
524
- color?: string;
525
- icon?: string;
526
- acl?: ioBroker.EvaluatedFileACL | MetaACL;
527
- }
528
-
529
- export type Folders = Record<string, FolderOrFileItem[]>;
530
-
531
- function sortFolders(a: FolderOrFileItem, b: FolderOrFileItem): number {
532
- if (a.folder && b.folder) {
533
- return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
534
- }
535
- if (a.folder) {
536
- return -1;
537
- }
538
- if (b.folder) {
539
- return 1;
540
- }
541
- return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
542
- }
543
-
544
- interface FileBrowserState {
545
- viewType: string;
546
- folders: Folders;
547
- filterEmpty: boolean;
548
- expanded: string[];
549
- currentDir: string;
550
- expertMode: boolean;
551
- addFolder: boolean;
552
- uploadFile: boolean | 'dragging';
553
- deleteItem: string;
554
- viewer: string;
555
- formatEditFile: string | null;
556
- path: string;
557
- selected: string;
558
- errorText: string;
559
- modalEditOfAccess: boolean;
560
- backgroundImage: string | null;
561
- queueLength: number;
562
- loadAllFolders: boolean;
563
- fileErrors: string[];
564
- filterByType: string;
565
- showTypesMenu: HTMLButtonElement | null;
566
- restrictToFolder: string;
567
- pathFocus: boolean;
568
- }
569
-
570
- export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserState> {
571
- private readonly imagePrefix: string;
572
-
573
- private readonly levelPadding: number;
574
-
575
- private mounted: boolean;
576
-
577
- private suppressDeleteConfirm: number;
578
-
579
- private browseList:
580
- | {
581
- processing?: boolean;
582
- resolve: null | ((files: ioBroker.ReadDirResult[]) => void);
583
- reject: null | ((e: any) => void);
584
- adapter: string | null;
585
- relPath: string | null;
586
- }[]
587
- | null;
588
-
589
- private browseListRunning: boolean;
590
-
591
- private initialReadFinished: boolean;
592
-
593
- private supportSubscribes: boolean | null;
594
-
595
- private _tempTimeout: Record<string, ReturnType<typeof setTimeout>>;
596
-
597
- private readonly limitToObjectID: string | null = null;
598
-
599
- private readonly limitToPath: string | null = null;
600
-
601
- private lastSelect: number | null = null;
602
-
603
- private setOpacityTimer: ReturnType<typeof setTimeout> | null = null;
604
-
605
- private cacheFoldersTimeout: ReturnType<typeof setTimeout> | null = null;
606
-
607
- private foldersLoading: boolean | null = null;
608
-
609
- private cacheFolders: Folders | null = null;
610
-
611
- private readonly localStorage: Storage;
612
-
613
- constructor(props: FileBrowserProps) {
614
- super(props);
615
-
616
- this.localStorage = (window as any)._localStorage || window.localStorage;
617
- const expandedStr = this.localStorage.getItem('files.expanded') || '[]';
618
-
619
- if (this.props.limitPath) {
620
- const parts = this.props.limitPath.split('/');
621
- this.limitToObjectID = parts[0];
622
- this.limitToPath = !parts.length ? null : parts.length === 1 && parts[0] === '' ? null : parts.join('/');
623
- if (this.limitToPath && this.limitToPath.endsWith('/')) {
624
- this.limitToPath.substring(0, this.limitToPath.length - 1);
625
- }
626
- }
627
-
628
- let expanded: string[];
629
- try {
630
- expanded = JSON.parse(expandedStr);
631
- if (this.limitToPath) {
632
- expanded = expanded.filter(
633
- id =>
634
- id.startsWith(`${this.limitToPath}/`) ||
635
- id === this.limitToPath ||
636
- this.limitToPath?.startsWith(`${id}/`),
637
- );
638
- }
639
- } catch {
640
- expanded = [];
641
- }
642
-
643
- let viewType;
644
- if (this.props.showViewTypeButton) {
645
- viewType = this.localStorage.getItem('files.viewType') || TABLE;
646
- } else {
647
- viewType = TABLE;
648
- }
649
-
650
- let selected = this.props.selected || this.localStorage.getItem('files.selected') || USER_DATA;
651
-
652
- let currentDir: string;
653
-
654
- if (props.restrictToFolder) {
655
- selected = props.restrictToFolder;
656
- currentDir = props.restrictToFolder;
657
- const parts = props.restrictToFolder.split('/');
658
- expanded = [];
659
- let path = '';
660
- for (let i = 0; i < parts.length; i++) {
661
- path += (path ? '/' : '') + parts[i];
662
- expanded.push(path);
663
- }
664
- } else {
665
- // TODO: Now we do not support multiple selection
666
- if (Array.isArray(selected)) {
667
- selected = selected[0];
668
- }
669
-
670
- if (isFile(selected)) {
671
- currentDir = getParentDir(selected);
672
- } else {
673
- currentDir = selected;
674
- }
675
- }
676
- const backgroundImage = this.localStorage.getItem('files.backgroundImage') || null;
677
-
678
- this.state = {
679
- viewType,
680
- folders: {},
681
- filterEmpty: this.localStorage.getItem('files.empty') !== 'false',
682
- expanded,
683
- currentDir,
684
- expertMode: !!props.expertMode,
685
- addFolder: false,
686
- uploadFile: false,
687
- deleteItem: '',
688
- // marked: [],
689
- viewer: '',
690
- formatEditFile: '',
691
- path: selected,
692
- selected,
693
- errorText: '',
694
- modalEditOfAccess: false,
695
- backgroundImage,
696
- queueLength: 0,
697
- loadAllFolders: false,
698
- // allFoldersLoaded: false,
699
- fileErrors: [],
700
- filterByType: props.filterByType || window.localStorage.getItem('files.filterByType') || '',
701
- showTypesMenu: null,
702
- restrictToFolder: props.restrictToFolder || '',
703
- pathFocus: false,
704
- };
705
-
706
- this.imagePrefix = this.props.imagePrefix || './files/';
707
-
708
- this.levelPadding = this.props.levelPadding || 20;
709
- this.mounted = true;
710
- this.suppressDeleteConfirm = 0;
711
-
712
- this.browseList = [];
713
- this.browseListRunning = false;
714
- this.initialReadFinished = false;
715
- this.supportSubscribes = null;
716
- this._tempTimeout = {};
717
- }
718
-
719
- static getDerivedStateFromProps(
720
- props: FileBrowserProps,
721
- state: FileBrowserState,
722
- ): Partial<FileBrowserState> | null {
723
- if (props.expertMode !== undefined && props.expertMode !== state.expertMode) {
724
- return { expertMode: props.expertMode, loadAllFolders: true };
725
- }
726
-
727
- return null;
728
- }
729
-
730
- async loadFolders(): Promise<void> {
731
- this.initialReadFinished = false;
732
-
733
- let folders = (await this.browseFolder('/')) as unknown as Folders;
734
-
735
- if (this.state.viewType === TABLE) {
736
- folders = (await this.browseFolders([...this.state.expanded], folders)) as unknown as Folders;
737
- } else if (
738
- this.state.currentDir &&
739
- this.state.currentDir !== '/' &&
740
- (!this.limitToObjectID || this.state.currentDir.startsWith(this.limitToObjectID))
741
- ) {
742
- folders = (await this.browseFolder(this.state.currentDir, folders)) as unknown as Folders;
743
- }
744
-
745
- this.setState({ folders }, () => {
746
- if (this.state.viewType === TABLE && !this.findItem(this.state.selected)) {
747
- const parts = this.state.selected.split('/');
748
- while (parts.length && !this.findItem(parts.join('/'))) {
749
- parts.pop();
750
- }
751
- let selected;
752
- if (parts.length) {
753
- selected = parts.join('/');
754
- } else {
755
- selected = USER_DATA;
756
- }
757
- this.setState({ selected, path: selected, pathFocus: false }, () => this.scrollToSelected());
758
- } else {
759
- this.scrollToSelected();
760
- }
761
- this.initialReadFinished = true;
762
- });
763
- }
764
-
765
- scrollToSelected(): void {
766
- if (this.mounted) {
767
- const el = document.getElementById(this.state.selected);
768
- el?.scrollIntoView();
769
- }
770
- }
771
-
772
- async componentDidMount(): Promise<void> {
773
- this.mounted = true;
774
- this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
775
-
776
- this.supportSubscribes = await this.props.socket.checkFeatureSupported('BINARY_STATE_EVENT');
777
- if (this.supportSubscribes) {
778
- await this.props.socket.subscribeFiles('*', '*', this.onFileChange);
779
- }
780
- }
781
-
782
- componentWillUnmount(): void {
783
- if (this.supportSubscribes) {
784
- this.props.socket.unsubscribeFiles('*', '*', this.onFileChange);
785
- }
786
- this.mounted = false;
787
- this.browseList = null;
788
- this.browseListRunning = false;
789
- Object.values(this._tempTimeout).forEach(timer => timer && clearTimeout(timer));
790
- this._tempTimeout = {};
791
- }
792
-
793
- browseFoldersCb(foldersList: string[], newFoldersNotNull: Folders, cb: (folders: Folders) => void): void {
794
- if (!foldersList?.length) {
795
- cb(newFoldersNotNull);
796
- } else {
797
- const folder = foldersList.shift();
798
- if (folder) {
799
- void this.browseFolder(folder, newFoldersNotNull)
800
- .catch((e: Error) => console.error(`Cannot read folder ${folder}: ${e.message}`))
801
- .then(() => {
802
- setTimeout(() => this.browseFoldersCb(foldersList, newFoldersNotNull, cb), 0);
803
- });
804
- } else {
805
- setTimeout(() => this.browseFoldersCb(foldersList, newFoldersNotNull, cb), 0);
806
- }
807
- }
808
- }
809
-
810
- browseFolders(foldersList: string[], _newFolders?: Folders | null): Promise<Folders> {
811
- let newFoldersNotNull: Folders;
812
- if (!_newFolders) {
813
- newFoldersNotNull = {};
814
- Object.keys(this.state.folders).forEach(folder => (newFoldersNotNull[folder] = this.state.folders[folder]));
815
- } else {
816
- newFoldersNotNull = _newFolders;
817
- }
818
-
819
- if (!foldersList?.length) {
820
- return Promise.resolve(newFoldersNotNull);
821
- }
822
- return new Promise(resolve => {
823
- this.browseFoldersCb(foldersList, newFoldersNotNull, resolve);
824
- });
825
- }
826
-
827
- readDirSerial(adapter: string, relPath: string): Promise<ioBroker.ReadDirResult[]> {
828
- return new Promise((resolve, reject) => {
829
- if (this.browseList) {
830
- // if component still mounted
831
- this.browseList.push({
832
- resolve: resolve as unknown as (files: ioBroker.ReadDirResult[]) => void,
833
- reject,
834
- adapter,
835
- relPath,
836
- });
837
- if (!this.browseListRunning) {
838
- this.processBrowseList();
839
- }
840
- }
841
- });
842
- }
843
-
844
- processBrowseList(level: number = 0): void {
845
- if (!this.browseListRunning && this.browseList && this.browseList.length) {
846
- this.browseListRunning = true;
847
- if (this.browseList.length > 10) {
848
- // not too often
849
- if (!(this.browseList.length % 10)) {
850
- this.setState({ queueLength: this.browseList.length });
851
- }
852
- } else {
853
- this.setState({ queueLength: this.browseList.length });
854
- }
855
-
856
- this.browseList[0].processing = true;
857
- this.props.socket
858
- .readDir(this.browseList[0].adapter, this.browseList[0].relPath as string)
859
- .then(files => {
860
- if (this.browseList) {
861
- // if component still mounted
862
- const item = this.browseList.shift();
863
- if (item) {
864
- const resolve = item.resolve;
865
- item.resolve = null;
866
- item.reject = null;
867
- item.adapter = null;
868
- item.relPath = null;
869
- if (resolve) {
870
- resolve(files);
871
- }
872
- this.browseListRunning = false;
873
- if (this.browseList.length) {
874
- if (level < 5) {
875
- this.processBrowseList(level + 1);
876
- } else {
877
- setTimeout(() => this.processBrowseList(0), 0);
878
- }
879
- } else {
880
- this.setState({ queueLength: 0 });
881
- }
882
- } else {
883
- this.setState({ queueLength: 0 });
884
- }
885
- }
886
- })
887
- .catch(e => {
888
- if (this.browseList) {
889
- // if component still mounted
890
- const item = this.browseList.shift();
891
- if (item) {
892
- const reject = item.reject;
893
- item.resolve = null;
894
- item.reject = null;
895
- item.adapter = null;
896
- item.relPath = null;
897
- if (reject) {
898
- reject(e);
899
- }
900
- this.browseListRunning = false;
901
- if (this.browseList.length) {
902
- if (level < 5) {
903
- this.processBrowseList(level + 1);
904
- } else {
905
- setTimeout(() => this.processBrowseList(0), 0);
906
- }
907
- } else {
908
- this.setState({ queueLength: 0 });
909
- }
910
- } else {
911
- this.setState({ queueLength: 0 });
912
- }
913
- }
914
- });
915
- }
916
- }
917
-
918
- async browseFolder(
919
- folderId: string,
920
- _newFolders?: Folders | null,
921
- _checkEmpty?: boolean,
922
- force?: boolean,
923
- ): Promise<Folders> {
924
- let newFoldersNotNull: Folders;
925
- if (!_newFolders) {
926
- newFoldersNotNull = {};
927
- Object.keys(this.state.folders).forEach(folder => {
928
- newFoldersNotNull[folder] = this.state.folders[folder];
929
- });
930
- } else {
931
- newFoldersNotNull = _newFolders;
932
- }
933
-
934
- if (newFoldersNotNull[folderId] && !force) {
935
- if (!_checkEmpty) {
936
- return new Promise((resolve, reject) => {
937
- Promise.all(
938
- newFoldersNotNull[folderId]
939
- .filter(item => item.folder)
940
- .map(item => this.browseFolder(item.id, newFoldersNotNull, true).catch(() => undefined)),
941
- )
942
- .then(() => resolve(newFoldersNotNull))
943
- .catch(error => reject(new Error(error)));
944
- });
945
- }
946
-
947
- return Promise.resolve(newFoldersNotNull);
948
- }
949
-
950
- // if root folder
951
- if (!folderId || folderId === '/') {
952
- try {
953
- let objs = (await this.props.socket.readMetaItems()) as MetaObject[];
954
- const _folders: FolderOrFileItem[] = [];
955
- let userData = null;
956
-
957
- if (this.state.restrictToFolder) {
958
- const adapter = this.state.restrictToFolder.split('/')[0];
959
- objs = objs.filter(obj => obj._id === adapter);
960
- } else if (!this.state.expertMode) {
961
- // load only adapter.admin and not other meta files like hm-rpc.0.devices.blablabla
962
- objs = objs.filter(obj => !obj._id.endsWith('.admin'));
963
- }
964
-
965
- const pos = objs.findIndex(obj => obj._id === 'system.meta.uuid');
966
- if (pos !== -1) {
967
- objs.splice(pos, 1);
968
- }
969
-
970
- objs.forEach(obj => {
971
- if (this.limitToObjectID && this.limitToObjectID !== obj._id) {
972
- return;
973
- }
974
-
975
- const item: FolderOrFileItem = {
976
- id: obj._id,
977
- name: obj._id,
978
- title: (obj.common && obj.common.name) || obj._id,
979
- meta: true,
980
- from: obj.from,
981
- ts: obj.ts,
982
- color: obj.common && obj.common.color,
983
- icon: obj.common && obj.common.icon,
984
- folder: true,
985
- acl: obj.acl,
986
- level: 0,
987
- };
988
-
989
- if (item.id === USER_DATA) {
990
- // user data must be first
991
- userData = item;
992
- } else {
993
- _folders.push(item);
994
- }
995
- });
996
-
997
- _folders.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
998
- if (!this.limitToObjectID || this.limitToObjectID === USER_DATA) {
999
- if (userData) {
1000
- _folders.unshift(userData);
1001
- }
1002
- }
1003
-
1004
- newFoldersNotNull[folderId || '/'] = _folders;
1005
-
1006
- if (!_checkEmpty) {
1007
- return Promise.all(
1008
- _folders
1009
- .filter(item => item.folder)
1010
- .map(item => this.browseFolder(item.id, newFoldersNotNull, true).catch(() => undefined)),
1011
- ).then(() => newFoldersNotNull);
1012
- }
1013
- } catch (e: unknown) {
1014
- const knownError = e as Error;
1015
- if (this.initialReadFinished) {
1016
- window.alert(`Cannot read meta items: ${knownError.message}`);
1017
- }
1018
- newFoldersNotNull[folderId || '/'] = [];
1019
- }
1020
- return newFoldersNotNull;
1021
- }
1022
-
1023
- const parts = folderId.split('/');
1024
- const level = parts.length;
1025
- const adapter = parts.shift();
1026
- const relPath = parts.join('/');
1027
-
1028
- // make all requests here serial
1029
- const files = await this.readDirSerial(adapter || '', relPath);
1030
- try {
1031
- const _folders: FolderOrFileItem[] = [];
1032
-
1033
- files.forEach(file => {
1034
- const item: FolderOrFileItem = {
1035
- id: `${folderId}/${file.file}`,
1036
- ext: Utils.getFileExtension(file.file),
1037
- folder: file.isDir,
1038
- name: file.file,
1039
- size: file.stats?.size,
1040
- modified: file.modifiedAt,
1041
- acl: file.acl,
1042
- level,
1043
- };
1044
-
1045
- if (this.state.restrictToFolder) {
1046
- if (
1047
- item.folder &&
1048
- (item.id.startsWith(`${this.state.restrictToFolder}/`) ||
1049
- item.id === this.state.restrictToFolder ||
1050
- this.state.restrictToFolder.startsWith(`${item.id}/`))
1051
- ) {
1052
- _folders.push(item);
1053
- } else if (item.id.startsWith(`${this.state.restrictToFolder}/`)) {
1054
- _folders.push(item);
1055
- }
1056
- } else if (this.limitToPath) {
1057
- if (
1058
- item.folder &&
1059
- (item.id.startsWith(`${this.limitToPath}/`) ||
1060
- item.id === this.limitToPath ||
1061
- this.limitToPath.startsWith(`${item.id}/`))
1062
- ) {
1063
- _folders.push(item);
1064
- } else if (item.id.startsWith(`${this.limitToPath}/`)) {
1065
- _folders.push(item);
1066
- }
1067
- } else {
1068
- _folders.push(item);
1069
- }
1070
- });
1071
-
1072
- _folders.sort(sortFolders);
1073
- newFoldersNotNull[folderId] = _folders;
1074
-
1075
- if (!_checkEmpty) {
1076
- return Promise.all(
1077
- _folders
1078
- .filter(item => item.folder)
1079
- .map(item => this.browseFolder(item.id, newFoldersNotNull, true)),
1080
- ).then(() => newFoldersNotNull);
1081
- }
1082
- } catch (e: unknown) {
1083
- const knownError = e as Error;
1084
- if (this.initialReadFinished) {
1085
- window.alert(`Cannot read ${adapter}${relPath ? `/${relPath}` : ''}: ${knownError?.message}`);
1086
- }
1087
- newFoldersNotNull[folderId] = [];
1088
- }
1089
-
1090
- return newFoldersNotNull;
1091
- }
1092
-
1093
- toggleFolder(item: FolderOrFileItem, e: React.MouseEvent<Element>): void {
1094
- e?.stopPropagation();
1095
- const expanded = [...this.state.expanded];
1096
- const pos = expanded.indexOf(item.id);
1097
- if (pos === -1) {
1098
- expanded.push(item.id);
1099
- expanded.sort();
1100
-
1101
- this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
1102
-
1103
- if (!item.temp) {
1104
- this.browseFolder(item.id)
1105
- .then(folders => this.setState({ expanded, folders }))
1106
- .catch(err =>
1107
- window.alert(
1108
- err === NOT_FOUND
1109
- ? this.props.t('ra_Cannot find "%s"', item.id)
1110
- : this.props.t('ra_Cannot read "%s"', item.id),
1111
- ),
1112
- );
1113
- } else {
1114
- this.setState({ expanded });
1115
- }
1116
- } else {
1117
- expanded.splice(pos, 1);
1118
- this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
1119
- this.setState({ expanded });
1120
- }
1121
- }
1122
-
1123
- onFileChange = (id: string, fileName: string, size: number | null): void => {
1124
- const key = `${id}/${fileName}`;
1125
- const pos = key.lastIndexOf('/');
1126
- const folder = key.substring(0, pos);
1127
- console.log(`File changed ${key}[${size}]`);
1128
-
1129
- if (this.state.folders[folder]) {
1130
- if (this._tempTimeout[folder]) {
1131
- clearTimeout(this._tempTimeout[folder]);
1132
- }
1133
-
1134
- this._tempTimeout[folder] = setTimeout(() => {
1135
- delete this._tempTimeout[folder];
1136
-
1137
- this.browseFolder(folder, null, false, true)
1138
- .then(folders => this.setState({ folders }))
1139
- .catch(e => console.error(`Cannot read folder: ${e.message}`));
1140
- }, 300);
1141
- }
1142
- };
1143
-
1144
- changeFolder(e: React.MouseEvent<HTMLDivElement>, folder?: string): void {
1145
- if (e) {
1146
- e.stopPropagation();
1147
- }
1148
-
1149
- this.lastSelect = Date.now();
1150
-
1151
- let _folder = folder || getParentDir(this.state.currentDir);
1152
-
1153
- if (_folder === '/') {
1154
- _folder = '';
1155
- }
1156
-
1157
- this.localStorage.setItem('files.currentDir', _folder);
1158
-
1159
- if (folder && e && (e.altKey || e.shiftKey || e.ctrlKey || e.metaKey)) {
1160
- return this.setState({ selected: _folder });
1161
- }
1162
-
1163
- if (_folder && !this.state.folders[_folder]) {
1164
- this.browseFolder(_folder)
1165
- .then(folders =>
1166
- this.setState(
1167
- {
1168
- folders,
1169
- path: _folder,
1170
- currentDir: _folder,
1171
- selected: _folder,
1172
- pathFocus: false,
1173
- },
1174
- () => this.props.onSelect && this.props.onSelect(''),
1175
- ),
1176
- )
1177
- .catch(_e => console.error(`Cannot read folder: ${_e.message}`));
1178
- return;
1179
- }
1180
-
1181
- return this.setState(
1182
- {
1183
- currentDir: _folder,
1184
- selected: _folder,
1185
- path: _folder,
1186
- pathFocus: false,
1187
- },
1188
- () => this.props.onSelect && this.props.onSelect(''),
1189
- );
1190
- }
1191
-
1192
- select(id: string, e?: React.MouseEvent<HTMLDivElement> | null, cb?: () => void): void {
1193
- if (e) {
1194
- e.stopPropagation();
1195
- }
1196
- this.lastSelect = Date.now();
1197
-
1198
- this.localStorage.setItem('files.selected', id);
1199
-
1200
- this.setState({ selected: id, path: id, pathFocus: false }, () => {
1201
- if (this.props.onSelect) {
1202
- const ext = Utils.getFileExtension(id);
1203
- if (
1204
- (!this.props.filterFiles || (ext && this.props.filterFiles.includes(ext))) &&
1205
- (!this.state.filterByType ||
1206
- (ext && (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(ext)))
1207
- ) {
1208
- this.props.onSelect(id, false, !!this.state.folders[id]);
1209
- } else {
1210
- this.props.onSelect('');
1211
- }
1212
- }
1213
- if (cb) {
1214
- cb();
1215
- }
1216
- });
1217
- }
1218
-
1219
- getText(text?: ioBroker.StringOrTranslated | null): string | undefined {
1220
- if (text) {
1221
- if (typeof text === 'object') {
1222
- return text[this.props.lang] || text.en || undefined;
1223
- }
1224
- return text;
1225
- }
1226
- return undefined;
1227
- }
1228
-
1229
- renderFolder(item: FolderOrFileItem, expanded?: boolean): React.JSX.Element | null {
1230
- if (
1231
- this.state.viewType === TABLE &&
1232
- this.state.filterEmpty &&
1233
- (!this.state.folders[item.id] || !this.state.folders[item.id].length) &&
1234
- item.id !== USER_DATA &&
1235
- !item.temp
1236
- ) {
1237
- return null;
1238
- }
1239
- const IconEl = expanded ? IconOpen : IconClosed;
1240
- const padding = this.state.viewType === TABLE ? item.level * this.levelPadding : 0;
1241
- const isUserData = item.name === USER_DATA;
1242
- const isSpecialData = isUserData || item.name === 'vis.0' || item.name === 'vis-2.0';
1243
-
1244
- return (
1245
- <Box
1246
- component="div"
1247
- key={item.id}
1248
- id={item.id}
1249
- style={this.state.viewType === TABLE ? { marginLeft: padding, width: `calc(100% - ${padding}px` } : {}}
1250
- onClick={e => (this.state.viewType === TABLE ? this.select(item.id, e) : this.changeFolder(e, item.id))}
1251
- onDoubleClick={e => this.state.viewType === TABLE && this.toggleFolder(item, e)}
1252
- title={this.getText(item.title)}
1253
- className="browserItem"
1254
- sx={Utils.getStyle(
1255
- this.props.theme,
1256
- styles[`item${this.state.viewType}`],
1257
- styles[`itemFolder${this.state.viewType}`],
1258
- this.state.selected === item.id ? styles.itemSelected : {},
1259
- item.temp ? styles.itemFolderTemp : {},
1260
- )}
1261
- >
1262
- <IconEl
1263
- style={Utils.getStyle(
1264
- this.props.theme,
1265
- styles[`itemFolderIcon${this.state.viewType}`],
1266
- isSpecialData && styles.specialFolder,
1267
- )}
1268
- onClick={
1269
- this.state.viewType === TABLE
1270
- ? (e: React.MouseEvent<Element>) => this.toggleFolder(item, e)
1271
- : undefined
1272
- }
1273
- />
1274
-
1275
- <Box
1276
- component="div"
1277
- sx={Utils.getStyle(
1278
- this.props.theme,
1279
- styles[`itemName${this.state.viewType}`],
1280
- styles[`itemNameFolder${this.state.viewType}`],
1281
- )}
1282
- >
1283
- {isUserData ? this.props.t('ra_User files') : item.name}
1284
- </Box>
1285
-
1286
- <Box
1287
- component="div"
1288
- style={styles[`itemSize${this.state.viewType}`]}
1289
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1290
- >
1291
- {this.state.viewType === TABLE && this.state.folders[item.id]
1292
- ? this.state.folders[item.id].length
1293
- : ''}
1294
- </Box>
1295
-
1296
- <Box
1297
- component="div"
1298
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1299
- >
1300
- {this.state.viewType === TABLE && this.props.expertMode ? this.formatAcl(item.acl) : null}
1301
- </Box>
1302
-
1303
- {this.state.viewType === TABLE && this.props.expertMode ? (
1304
- <Box
1305
- component="div"
1306
- sx={{ ...styles.itemDeleteButtonTable, display: { md: 'inline-block', sm: 'none' } }}
1307
- />
1308
- ) : null}
1309
-
1310
- {this.state.viewType === TABLE && this.props.allowDownload ? (
1311
- <div style={styles[`itemDownloadEmpty${this.state.viewType}`]} />
1312
- ) : null}
1313
-
1314
- {this.state.viewType === TABLE &&
1315
- this.props.allowDelete &&
1316
- this.state.folders[item.id] &&
1317
- this.state.folders[item.id].length ? (
1318
- <IconButton
1319
- aria-label="delete"
1320
- onClick={e => {
1321
- e.stopPropagation();
1322
- if (this.suppressDeleteConfirm > Date.now()) {
1323
- this.deleteItem(item.id);
1324
- } else {
1325
- this.setState({ deleteItem: item.id });
1326
- }
1327
- }}
1328
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1329
- size="large"
1330
- >
1331
- <DeleteIcon fontSize="small" />
1332
- </IconButton>
1333
- ) : this.state.viewType === TABLE && this.props.allowDelete ? (
1334
- <Box
1335
- component="div"
1336
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1337
- />
1338
- ) : null}
1339
- </Box>
1340
- );
1341
- }
1342
-
1343
- renderBackFolder(): React.JSX.Element {
1344
- return (
1345
- <Box
1346
- component="div"
1347
- key={this.state.currentDir}
1348
- id={this.state.currentDir}
1349
- onClick={e => this.changeFolder(e)}
1350
- title={this.props.t('ra_Back to %s', getParentDir(this.state.currentDir))}
1351
- className="browserItem"
1352
- sx={Utils.getStyle(
1353
- this.props.theme,
1354
- styles[`item${this.state.viewType}`],
1355
- styles[`itemFolder${this.state.viewType}`],
1356
- )}
1357
- >
1358
- <IconClosed style={styles[`itemFolderIcon${this.state.viewType}`]} />
1359
- <IconBack sx={styles.itemFolderIconBack} />
1360
-
1361
- <Box
1362
- component="div"
1363
- sx={Utils.getStyle(
1364
- this.props.theme,
1365
- styles[`itemName${this.state.viewType}`],
1366
- styles[`itemNameFolder${this.state.viewType}`],
1367
- )}
1368
- >
1369
- ..
1370
- </Box>
1371
- </Box>
1372
- );
1373
- }
1374
-
1375
- formatSize(size: number | null | undefined): React.JSX.Element {
1376
- return (
1377
- <div style={styles[`itemSize${this.state.viewType}`]}>
1378
- {size || size === 0 ? Utils.formatBytes(size) : ''}
1379
- </div>
1380
- );
1381
- }
1382
-
1383
- formatAcl(acl: ioBroker.EvaluatedFileACL | MetaACL | undefined): React.JSX.Element {
1384
- const access: number = acl ? (acl as ioBroker.EvaluatedFileACL).permissions || (acl as MetaACL).file : 0;
1385
- let accessStr: string;
1386
- if (access) {
1387
- accessStr = access.toString(16).padStart(3, '0');
1388
- } else {
1389
- accessStr = '';
1390
- }
1391
-
1392
- return (
1393
- <div style={styles[`itemAccess${this.state.viewType}`]}>
1394
- {this.props.modalEditOfAccessControl ? (
1395
- <IconButton
1396
- size="large"
1397
- onClick={() => this.setState({ modalEditOfAccess: true })}
1398
- sx={styles[`itemAclButton${this.state.viewType}`]}
1399
- >
1400
- {accessStr || '---'}
1401
- </IconButton>
1402
- ) : (
1403
- accessStr || '---'
1404
- )}
1405
- </div>
1406
- );
1407
- }
1408
-
1409
- getFileIcon(ext: string | null): React.JSX.Element {
1410
- switch (ext) {
1411
- case 'json':
1412
- case 'json5':
1413
- return <JsonIcon style={styles[`itemIcon${this.state.viewType}`]} />;
1414
-
1415
- case 'css':
1416
- return <CssIcon style={styles[`itemIcon${this.state.viewType}`]} />;
1417
-
1418
- case 'js':
1419
- case 'ts':
1420
- return <JSIcon style={styles[`itemIcon${this.state.viewType}`]} />;
1421
-
1422
- case 'html':
1423
- case 'md':
1424
- return <HtmlIcon style={styles[`itemIcon${this.state.viewType}`]} />;
1425
-
1426
- case 'mp3':
1427
- case 'ogg':
1428
- case 'wav':
1429
- case 'm4a':
1430
- case 'mp4':
1431
- case 'flac':
1432
- return <MusicIcon style={styles[`itemIcon${this.state.viewType}`]} />;
1433
-
1434
- default:
1435
- return <FileIcon style={styles[`itemIcon${this.state.viewType}`]} />;
1436
- }
1437
- }
1438
-
1439
- static getEditFile(ext: string | null): boolean {
1440
- switch (ext) {
1441
- case 'json':
1442
- case 'json5':
1443
- case 'js':
1444
- case 'html':
1445
- case 'txt':
1446
- case 'css':
1447
- case 'log':
1448
- return true;
1449
- default:
1450
- return false;
1451
- }
1452
- }
1453
-
1454
- setStateBackgroundImage = (): void => {
1455
- const array = ['light', 'dark', 'colored', 'delete'];
1456
- this.setState(({ backgroundImage }) => {
1457
- if (
1458
- backgroundImage &&
1459
- array.indexOf(backgroundImage) !== -1 &&
1460
- array.length - 1 !== array.indexOf(backgroundImage)
1461
- ) {
1462
- this.localStorage.setItem('files.backgroundImage', array[array.indexOf(backgroundImage) + 1]);
1463
- return { backgroundImage: array[array.indexOf(backgroundImage) + 1] };
1464
- }
1465
- this.localStorage.setItem('files.backgroundImage', array[0]);
1466
- return { backgroundImage: array[0] };
1467
- });
1468
- };
1469
-
1470
- getStyleBackgroundImage = (): React.CSSProperties | null => {
1471
- // ['light', 'dark', 'colored', 'delete']
1472
- switch (this.state.backgroundImage) {
1473
- case 'light':
1474
- return styles.backgroundImageLight;
1475
- case 'dark':
1476
- return styles.backgroundImageDark;
1477
- case 'colored':
1478
- return styles.backgroundImageColored;
1479
- case 'delete':
1480
- return null;
1481
- default:
1482
- return null;
1483
- }
1484
- };
1485
-
1486
- renderFile(item: FolderOrFileItem): React.JSX.Element {
1487
- const padding = this.state.viewType === TABLE ? item.level * this.levelPadding : 0;
1488
- const ext = Utils.getFileExtension(item.name);
1489
-
1490
- return (
1491
- <Box
1492
- component="div"
1493
- key={item.id}
1494
- id={item.id}
1495
- onDoubleClick={e => {
1496
- e.stopPropagation();
1497
- if (!this.props.onSelect) {
1498
- this.setState({ viewer: this.imagePrefix + item.id, formatEditFile: ext });
1499
- } else if (
1500
- (!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1501
- (!this.state.filterByType ||
1502
- (item.ext &&
1503
- (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(item.ext)))
1504
- ) {
1505
- this.props.onSelect(item.id, true, !!this.state.folders[item.id]);
1506
- }
1507
- }}
1508
- onClick={e => this.select(item.id, e)}
1509
- style={this.state.viewType === TABLE ? { marginLeft: padding, width: `calc(100% - ${padding}px)` } : {}}
1510
- className="browserItem"
1511
- sx={Utils.getStyle(
1512
- this.props.theme,
1513
- styles[`item${this.state.viewType}`],
1514
- styles[`itemFile${this.state.viewType}`],
1515
- this.state.selected === item.id ? styles.itemSelected : undefined,
1516
- )}
1517
- >
1518
- {ext && EXTENSIONS.images.includes(ext) ? (
1519
- this.state.fileErrors.includes(item.id) ? (
1520
- <IconNoIcon
1521
- style={{
1522
- ...styles[`itemImage${this.state.viewType}`],
1523
- ...this.getStyleBackgroundImage(),
1524
- ...styles[`itemNoImage${this.state.viewType}`],
1525
- }}
1526
- />
1527
- ) : (
1528
- <Icon
1529
- onError={e => {
1530
- (e.target as HTMLImageElement).onerror = null;
1531
- const fileErrors = [...this.state.fileErrors];
1532
- if (!fileErrors.includes(item.id)) {
1533
- fileErrors.push(item.id);
1534
- this.setState({ fileErrors });
1535
- }
1536
- }}
1537
- style={{ ...styles[`itemImage${this.state.viewType}`], ...this.getStyleBackgroundImage() }}
1538
- src={this.imagePrefix + item.id}
1539
- alt={item.name}
1540
- />
1541
- )
1542
- ) : (
1543
- this.getFileIcon(ext)
1544
- )}
1545
- <Box
1546
- component="div"
1547
- sx={styles[`itemName${this.state.viewType}`]}
1548
- >
1549
- {item.name}
1550
- </Box>
1551
- <Box
1552
- component="div"
1553
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1554
- >
1555
- {this.formatSize(item.size)}
1556
- </Box>
1557
- <Box
1558
- component="div"
1559
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1560
- >
1561
- {this.state.viewType === TABLE && this.props.expertMode ? this.formatAcl(item.acl) : null}
1562
- </Box>
1563
- <Box
1564
- component="div"
1565
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1566
- >
1567
- {this.state.viewType === TABLE && this.props.expertMode && FileBrowserClass.getEditFile(ext) ? (
1568
- <IconButton
1569
- aria-label="edit"
1570
- onClick={e => {
1571
- e.stopPropagation();
1572
- if (!this.props.onSelect) {
1573
- this.setState({ viewer: this.imagePrefix + item.id, formatEditFile: ext });
1574
- } else if (
1575
- (!this.props.filterFiles ||
1576
- (item.ext && this.props.filterFiles.includes(item.ext))) &&
1577
- (!this.state.filterByType ||
1578
- (item.ext &&
1579
- (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(
1580
- item.ext,
1581
- )))
1582
- ) {
1583
- this.props.onSelect(item.id, true, !!this.state.folders[item.id]);
1584
- }
1585
- }}
1586
- sx={styles.itemDeleteButtonTable}
1587
- size="large"
1588
- >
1589
- <EditIcon fontSize="small" />
1590
- </IconButton>
1591
- ) : (
1592
- <Box
1593
- component="div"
1594
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1595
- />
1596
- )}
1597
- </Box>
1598
- {this.state.viewType === TABLE && this.props.allowDownload ? (
1599
- <Box
1600
- component="a"
1601
- className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge"
1602
- sx={styles.itemDownloadButtonTable}
1603
- tabIndex={0}
1604
- download={item.id}
1605
- href={this.imagePrefix + item.id}
1606
- onClick={e => e.stopPropagation()}
1607
- >
1608
- <DownloadIcon />
1609
- </Box>
1610
- ) : null}
1611
-
1612
- {this.state.viewType === TABLE &&
1613
- this.props.allowDelete &&
1614
- item.id !== 'vis.0/' &&
1615
- item.id !== 'vis-2.0/' &&
1616
- item.id !== USER_DATA ? (
1617
- <IconButton
1618
- aria-label="delete"
1619
- onClick={e => {
1620
- e.stopPropagation();
1621
- if (this.suppressDeleteConfirm > Date.now()) {
1622
- this.deleteItem(item.id);
1623
- } else {
1624
- this.setState({ deleteItem: item.id });
1625
- }
1626
- }}
1627
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1628
- size="large"
1629
- >
1630
- <DeleteIcon fontSize="small" />
1631
- </IconButton>
1632
- ) : this.state.viewType === TABLE && this.props.allowDelete ? (
1633
- <Box
1634
- component="div"
1635
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1636
- />
1637
- ) : null}
1638
- </Box>
1639
- );
1640
- }
1641
-
1642
- renderItems(folderId: string): React.JSX.Element | (React.JSX.Element | null)[] {
1643
- if (this.state.folders && this.state.folders[folderId]) {
1644
- // tile
1645
- if (this.state.viewType === TILE) {
1646
- const res: (React.JSX.Element | null)[] = [];
1647
- if (folderId && folderId !== '/') {
1648
- res.push(this.renderBackFolder());
1649
- }
1650
- this.state.folders[folderId].forEach(item => {
1651
- if (item.folder) {
1652
- res.push(this.renderFolder(item));
1653
- } else if (
1654
- (!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1655
- (!this.state.filterByType ||
1656
- (item.ext &&
1657
- (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(item.ext)))
1658
- ) {
1659
- res.push(this.renderFile(item));
1660
- }
1661
- });
1662
- return res;
1663
- }
1664
-
1665
- const totalResult: (React.JSX.Element | null)[] = [];
1666
- this.state.folders[folderId].forEach(item => {
1667
- if (item.folder) {
1668
- const expanded = this.state.expanded.includes(item.id);
1669
-
1670
- const folders = this.renderFolder(item, expanded);
1671
- if (Array.isArray(folders)) {
1672
- folders.forEach(folder => totalResult.push(folder));
1673
- } else {
1674
- totalResult.push(folders);
1675
- }
1676
- if (this.state.folders[item.id] && expanded) {
1677
- const items = this.renderItems(item.id);
1678
- if (Array.isArray(items)) {
1679
- items.forEach(_item => totalResult.push(_item));
1680
- } else {
1681
- totalResult.push(items);
1682
- }
1683
- }
1684
- } else if (
1685
- (!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1686
- (!this.state.filterByType ||
1687
- (item.ext &&
1688
- (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(item.ext)))
1689
- ) {
1690
- totalResult.push(this.renderFile(item));
1691
- }
1692
- });
1693
-
1694
- return totalResult;
1695
- }
1696
-
1697
- return (
1698
- <div style={{ position: 'relative' }}>
1699
- <CircularProgress
1700
- key={folderId}
1701
- color="secondary"
1702
- size={24}
1703
- />
1704
- <div
1705
- style={{
1706
- position: 'absolute',
1707
- zIndex: 2,
1708
- top: 4,
1709
- width: 24,
1710
- textAlign: 'center',
1711
- }}
1712
- >
1713
- {this.state.queueLength}
1714
- </div>
1715
- </div>
1716
- );
1717
- }
1718
-
1719
- renderToolbar(): React.JSX.Element {
1720
- const IconType: React.FC<{ fontSize?: 'small' }> | null = this.props.showTypeSelector
1721
- ? FILE_TYPE_ICONS[this.state.filterByType || 'all'] || FILE_TYPE_ICONS.all
1722
- : null;
1723
-
1724
- const isInFolder = this.findFirstFolder(this.state.selected);
1725
-
1726
- return (
1727
- <Toolbar
1728
- key="toolbar"
1729
- variant="dense"
1730
- >
1731
- {this.props.allowNonRestricted && this.props.restrictToFolder ? (
1732
- <IconButton
1733
- edge="start"
1734
- title={
1735
- this.state.restrictToFolder
1736
- ? this.props.t('ra_Show all folders')
1737
- : this.props.t('ra_Restrict to folder')
1738
- }
1739
- style={{
1740
- ...styles.menuButton,
1741
- ...(this.state.restrictToFolder ? styles.menuButtonRestrictActive : undefined),
1742
- }}
1743
- aria-label="restricted to folder"
1744
- onClick={() =>
1745
- this.setState({
1746
- restrictToFolder:
1747
- (this.state.restrictToFolder ? '' : this.props.restrictToFolder) || '',
1748
- loadAllFolders: true,
1749
- })
1750
- }
1751
- size="small"
1752
- >
1753
- <RestrictedIcon fontSize="small" />
1754
- </IconButton>
1755
- ) : null}
1756
- {this.props.showExpertButton ? (
1757
- <IconButton
1758
- edge="start"
1759
- title={this.props.t('ra_Toggle expert mode')}
1760
- style={{
1761
- ...styles.menuButton,
1762
- ...(this.state.expertMode ? styles.menuButtonExpertActive : undefined),
1763
- }}
1764
- aria-label="expert mode"
1765
- onClick={() => this.setState({ expertMode: !this.state.expertMode })}
1766
- size="small"
1767
- >
1768
- <IconExpert />
1769
- </IconButton>
1770
- ) : null}
1771
- {this.props.showViewTypeButton ? (
1772
- <IconButton
1773
- edge="start"
1774
- title={this.props.t('ra_Toggle view mode')}
1775
- style={styles.menuButton}
1776
- aria-label="view mode"
1777
- onClick={() => {
1778
- const viewType = this.state.viewType === TABLE ? TILE : TABLE;
1779
- this.localStorage.setItem('files.viewType', viewType);
1780
- let currentDir = this.state.selected;
1781
- if (isFile(currentDir)) {
1782
- currentDir = getParentDir(currentDir);
1783
- }
1784
- this.setState({ viewType, currentDir }, () => {
1785
- if (this.state.viewType === TABLE) {
1786
- this.scrollToSelected();
1787
- }
1788
- });
1789
- }}
1790
- size="small"
1791
- >
1792
- {this.state.viewType !== TABLE ? <IconList fontSize="small" /> : <IconTile fontSize="small" />}
1793
- </IconButton>
1794
- ) : null}
1795
- <IconButton
1796
- edge="start"
1797
- title={this.props.t('ra_Hide empty folders')}
1798
- style={styles.menuButton}
1799
- color={this.state.filterEmpty ? 'secondary' : 'inherit'}
1800
- aria-label="filter empty"
1801
- onClick={() => {
1802
- this.localStorage.setItem('file.empty', this.state.filterEmpty ? 'false' : 'true');
1803
- this.setState({ filterEmpty: !this.state.filterEmpty });
1804
- }}
1805
- size="small"
1806
- >
1807
- <EmptyFilterIcon fontSize="small" />
1808
- </IconButton>
1809
- <IconButton
1810
- edge="start"
1811
- title={this.props.t('ra_Reload files')}
1812
- style={styles.menuButton}
1813
- color="inherit"
1814
- aria-label="reload files"
1815
- onClick={() => this.setState({ folders: {} }, () => this.loadFolders())}
1816
- size="small"
1817
- >
1818
- <RefreshIcon fontSize="small" />
1819
- </IconButton>
1820
- {this.props.allowCreateFolder ? (
1821
- <IconButton
1822
- edge="start"
1823
- disabled={
1824
- !this.state.selected ||
1825
- !isInFolder ||
1826
- (!!this.limitToPath &&
1827
- !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1828
- this.limitToPath !== this.state.selected)
1829
- }
1830
- title={this.props.t('ra_Create folder')}
1831
- style={styles.menuButton}
1832
- color="inherit"
1833
- aria-label="add folder"
1834
- onClick={() => this.setState({ addFolder: true })}
1835
- size="small"
1836
- >
1837
- <AddFolderIcon fontSize="small" />
1838
- </IconButton>
1839
- ) : null}
1840
- {this.props.allowUpload ? (
1841
- <IconButton
1842
- edge="start"
1843
- disabled={
1844
- !this.state.selected ||
1845
- !isInFolder ||
1846
- (!!this.limitToPath &&
1847
- !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1848
- this.limitToPath !== this.state.selected)
1849
- }
1850
- title={this.props.t('ra_Upload file')}
1851
- style={styles.menuButton}
1852
- color="inherit"
1853
- aria-label="upload file"
1854
- onClick={() => this.setState({ uploadFile: true })}
1855
- size="small"
1856
- >
1857
- <UploadIcon fontSize="small" />
1858
- </IconButton>
1859
- ) : null}
1860
- {this.props.showTypeSelector && IconType ? (
1861
- <Tooltip
1862
- title={this.props.t('ra_Filter files')}
1863
- slotProps={{ popper: { sx: styles.tooltip } }}
1864
- >
1865
- <IconButton
1866
- size="small"
1867
- onClick={e => this.setState({ showTypesMenu: e.target as HTMLButtonElement })}
1868
- >
1869
- <IconType fontSize="small" />
1870
- </IconButton>
1871
- </Tooltip>
1872
- ) : null}
1873
- {this.state.showTypesMenu ? (
1874
- <Menu
1875
- open={!0}
1876
- anchorEl={this.state.showTypesMenu}
1877
- onClose={() => this.setState({ showTypesMenu: null })}
1878
- >
1879
- {Object.keys(FILE_TYPE_ICONS).map(type => {
1880
- const MyIcon: React.FC<{ fontSize?: 'small' }> = FILE_TYPE_ICONS[type];
1881
- return (
1882
- <MenuItem
1883
- key={type}
1884
- selected={this.state.filterByType === type}
1885
- onClick={() => {
1886
- if (type === 'all') {
1887
- this.localStorage.removeItem('files.filterByType');
1888
- this.setState({ filterByType: '', showTypesMenu: null });
1889
- } else {
1890
- this.localStorage.setItem('files.filterByType', type);
1891
- this.setState({ filterByType: type, showTypesMenu: null });
1892
- }
1893
- }}
1894
- >
1895
- <ListItemIcon>
1896
- <MyIcon fontSize="small" />
1897
- </ListItemIcon>
1898
- <ListItemText>{this.props.t(`ra_fileType_${type}`)}</ListItemText>
1899
- </MenuItem>
1900
- );
1901
- })}
1902
- </Menu>
1903
- ) : null}
1904
- <Tooltip
1905
- title={this.props.t('ra_Background image')}
1906
- slotProps={{ popper: { sx: styles.tooltip } }}
1907
- >
1908
- <IconButton
1909
- color="inherit"
1910
- edge="start"
1911
- style={styles.menuButton}
1912
- onClick={this.setStateBackgroundImage}
1913
- size="small"
1914
- >
1915
- <Brightness5Icon fontSize="small" />
1916
- </IconButton>
1917
- </Tooltip>
1918
- {this.state.viewType !== TABLE && this.props.allowDelete ? (
1919
- <Tooltip
1920
- title={this.props.t('ra_Delete')}
1921
- slotProps={{ popper: { sx: styles.tooltip } }}
1922
- >
1923
- <span>
1924
- <IconButton
1925
- aria-label="delete"
1926
- disabled={
1927
- !this.state.selected ||
1928
- this.state.selected === 'vis.0/' ||
1929
- this.state.selected === 'vis-2.0/' ||
1930
- this.state.selected === USER_DATA
1931
- }
1932
- color="inherit"
1933
- edge="start"
1934
- style={styles.menuButton}
1935
- onClick={e => {
1936
- e.stopPropagation();
1937
- if (this.suppressDeleteConfirm > Date.now()) {
1938
- this.deleteItem(this.state.selected);
1939
- } else {
1940
- this.setState({ deleteItem: this.state.selected });
1941
- }
1942
- }}
1943
- size="small"
1944
- >
1945
- <DeleteIcon fontSize="small" />
1946
- </IconButton>
1947
- </span>
1948
- </Tooltip>
1949
- ) : null}
1950
- </Toolbar>
1951
- );
1952
- }
1953
-
1954
- findItem(id: string, folders?: Folders | null): null | FolderOrFileItem {
1955
- folders = folders || this.state.folders;
1956
- if (!folders) {
1957
- return null;
1958
- }
1959
- const parts = id.split('/');
1960
- parts.pop();
1961
- const parentFolder = parts.join('/') || '/';
1962
- if (!folders[parentFolder]) {
1963
- return null;
1964
- }
1965
- return folders[parentFolder].find(item => item.id === id) || null;
1966
- }
1967
-
1968
- renderInputDialog(): React.JSX.Element | null {
1969
- if (this.state.addFolder) {
1970
- const parentFolder = this.findFirstFolder(this.state.selected);
1971
-
1972
- if (!parentFolder) {
1973
- window.alert(this.props.t('ra_Invalid parent folder!'));
1974
- return null;
1975
- }
1976
-
1977
- return (
1978
- <TextInputDialog
1979
- key="inputDialog"
1980
- applyText={this.props.t('ra_Create')}
1981
- cancelText={this.props.t('ra_Cancel')}
1982
- titleText={this.props.t('ra_Create new folder in %s', this.state.selected)}
1983
- promptText={this.props.t(
1984
- 'ra_If no file will be created in the folder, it will disappear after the browser closed',
1985
- )}
1986
- labelText={this.props.t('ra_Folder name')}
1987
- verify={(text: string) =>
1988
- this.state.folders[parentFolder].find(item => item.name === text)
1989
- ? ''
1990
- : this.props.t('ra_Duplicate name')
1991
- }
1992
- onClose={(name: string | null) => {
1993
- if (name) {
1994
- const folders: Folders = {};
1995
- Object.keys(this.state.folders).forEach(
1996
- folder => (folders[folder] = this.state.folders[folder]),
1997
- );
1998
- const parent = this.findItem(parentFolder);
1999
- const id = `${parentFolder}/${name}`;
2000
- folders[parentFolder].push({
2001
- id,
2002
- level: (parent?.level || 0) + 1,
2003
- name,
2004
- folder: true,
2005
- temp: true,
2006
- });
2007
-
2008
- folders[parentFolder].sort(sortFolders);
2009
-
2010
- folders[id] = [];
2011
- const expanded = [...this.state.expanded];
2012
- if (!expanded.includes(parentFolder)) {
2013
- expanded.push(parentFolder);
2014
- expanded.sort();
2015
- }
2016
- this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
2017
- this.setState({ addFolder: false, folders, expanded }, () => this.select(id));
2018
- } else {
2019
- this.setState({ addFolder: false });
2020
- }
2021
- }}
2022
- replace={(text: string) => text.replace(/[^-_\w]/, '_')}
2023
- />
2024
- );
2025
- }
2026
- return null;
2027
- }
2028
-
2029
- componentDidUpdate(/* prevProps , prevState, snapshot */): void {
2030
- if (this.setOpacityTimer) {
2031
- clearTimeout(this.setOpacityTimer);
2032
- }
2033
- this.setOpacityTimer = setTimeout(() => {
2034
- this.setOpacityTimer = null;
2035
- const items = window.document.getElementsByClassName('browserItem');
2036
- for (let i = 0; i < items.length; i++) {
2037
- (items[i] as HTMLElement).style.opacity = '1';
2038
- }
2039
- }, 100);
2040
- }
2041
-
2042
- findFirstFolder(id: string): string | null {
2043
- let parentFolder = id;
2044
- const item = this.findItem(parentFolder);
2045
- // find folder
2046
- if (item && !item.folder) {
2047
- const parts = parentFolder.split('/');
2048
- parts.pop();
2049
- parentFolder = '';
2050
- while (parts.length) {
2051
- const _item = this.findItem(parts.join('/'));
2052
- if (_item?.folder) {
2053
- parentFolder = parts.join('/');
2054
- break;
2055
- }
2056
- parts.pop();
2057
- }
2058
- if (!parts.length) {
2059
- return null;
2060
- }
2061
- }
2062
-
2063
- return parentFolder;
2064
- }
2065
-
2066
- async uploadFile(fileName: string, data: string): Promise<void> {
2067
- const parts: string[] = fileName.split('/');
2068
- const adapterName = parts.shift();
2069
- try {
2070
- await this.props.socket.writeFile64(adapterName || '', parts.join('/'), data);
2071
- } catch (e: unknown) {
2072
- const knownError = e as Error;
2073
- window.alert(`Cannot write file: ${knownError?.message}`);
2074
- }
2075
- }
2076
-
2077
- renderUpload(): React.JSX.Element[] | null {
2078
- if (this.state.uploadFile) {
2079
- return [
2080
- <Fab
2081
- key="close"
2082
- color="primary"
2083
- aria-label="close"
2084
- style={styles.uploadCloseButton}
2085
- onClick={() => this.setState({ uploadFile: false })}
2086
- >
2087
- <CloseIcon />
2088
- </Fab>,
2089
- <Dropzone
2090
- key="dropzone"
2091
- onDragEnter={() => this.setState({ uploadFile: 'dragging' })}
2092
- onDragLeave={() => this.setState({ uploadFile: true })}
2093
- onDrop={acceptedFiles => {
2094
- let count = acceptedFiles.length;
2095
-
2096
- acceptedFiles.forEach(file => {
2097
- const reader = new FileReader();
2098
-
2099
- reader.onabort = () => console.log('file reading was aborted');
2100
- reader.onerror = () => console.log('file reading has failed');
2101
- reader.onload = () => {
2102
- const parentFolder = this.findFirstFolder(this.state.selected);
2103
-
2104
- if (!parentFolder) {
2105
- window.alert(this.props.t('ra_Invalid parent folder!'));
2106
- } else {
2107
- const id = `${parentFolder}/${file.name}`;
2108
-
2109
- void this.uploadFile(id, reader.result as string).then(() => {
2110
- if (!--count) {
2111
- this.setState({ uploadFile: false }, () => {
2112
- if (this.supportSubscribes) {
2113
- // open current folder
2114
- const expanded = [...this.state.expanded];
2115
- if (!expanded.includes(parentFolder)) {
2116
- expanded.push(parentFolder);
2117
- expanded.sort();
2118
- this.localStorage.setItem(
2119
- 'files.expanded',
2120
- JSON.stringify(expanded),
2121
- );
2122
- }
2123
- this.setState({ expanded }, () => this.select(id));
2124
- } else {
2125
- setTimeout(
2126
- () =>
2127
- this.browseFolder(parentFolder, null, false, true).then(
2128
- folders => {
2129
- // open current folder
2130
- const expanded = [...this.state.expanded];
2131
- if (!expanded.includes(parentFolder)) {
2132
- expanded.push(parentFolder);
2133
- expanded.sort();
2134
- this.localStorage.setItem(
2135
- 'files.expanded',
2136
- JSON.stringify(expanded),
2137
- );
2138
- }
2139
- this.setState({ folders, expanded }, () =>
2140
- this.select(id),
2141
- );
2142
- },
2143
- ),
2144
- 500,
2145
- );
2146
- }
2147
- });
2148
- }
2149
- });
2150
- }
2151
- };
2152
-
2153
- reader.readAsArrayBuffer(file);
2154
- });
2155
- }}
2156
- >
2157
- {({ getRootProps, getInputProps }) => (
2158
- <div
2159
- style={{
2160
- ...styles.uploadDiv,
2161
- ...(this.state.uploadFile === 'dragging' ? styles.uploadDivDragging : undefined),
2162
- }}
2163
- {...getRootProps()}
2164
- >
2165
- <input {...getInputProps()} />
2166
- <Box
2167
- component="div"
2168
- sx={styles.uploadCenterDiv}
2169
- >
2170
- <div style={styles.uploadCenterTextAndIcon}>
2171
- <UploadIcon style={styles.uploadCenterIcon} />
2172
- <div style={styles.uploadCenterText}>
2173
- {this.state.uploadFile === 'dragging'
2174
- ? this.props.t('ra_Drop file here')
2175
- : this.props.t(
2176
- 'ra_Place your files here or click here to open the browse dialog',
2177
- )}
2178
- </div>
2179
- </div>
2180
- </Box>
2181
- </div>
2182
- )}
2183
- </Dropzone>,
2184
- ];
2185
- }
2186
- return null;
2187
- }
2188
-
2189
- deleteRecursive(id: string): Promise<void> {
2190
- const item = this.findItem(id);
2191
- if (item?.folder) {
2192
- return (
2193
- this.state.folders[id]
2194
- ? Promise.all(this.state.folders[id].map(_item => this.deleteRecursive(_item.id)))
2195
- : Promise.resolve()
2196
- ).then(() => {
2197
- // If it is a folder of second level
2198
- if (item.level >= 1) {
2199
- const parts = id.split('/');
2200
- const adapter = parts.shift();
2201
- void this.props.socket.deleteFolder(adapter || '', parts.join('/')).then(() => {
2202
- // remove this folder
2203
- const folders = JSON.parse(JSON.stringify(this.state.folders));
2204
- delete folders[item.id];
2205
- // delete folder from parent item
2206
- const parentId = getParentDir(item.id);
2207
- const parentFolder = folders[parentId];
2208
- if (parentFolder) {
2209
- const pos = parentFolder.findIndex((f: FolderOrFileItem) => f.id === item.id);
2210
- if (pos !== -1) {
2211
- parentFolder.splice(pos, 1);
2212
- }
2213
-
2214
- this.select(parentId, null, () => this.setState({ folders }));
2215
- }
2216
- });
2217
- }
2218
- });
2219
- }
2220
-
2221
- const parts = id.split('/');
2222
- const adapter = parts.shift();
2223
- if (parts.length) {
2224
- return this.props.socket
2225
- .deleteFile(adapter || '', parts.join('/'))
2226
- .catch(e => window.alert(`Cannot delete file: ${e}`));
2227
- }
2228
- return Promise.resolve();
2229
- }
2230
-
2231
- deleteItem(deleteItem: string): void {
2232
- deleteItem = deleteItem || this.state.deleteItem;
2233
-
2234
- this.setState({ deleteItem: '' }, () =>
2235
- this.deleteRecursive(deleteItem).then(() => {
2236
- const newState: Partial<FileBrowserState> = {};
2237
- const pos = this.state.expanded.indexOf(deleteItem);
2238
- if (pos !== -1) {
2239
- const expanded = [...this.state.expanded];
2240
- expanded.splice(pos, 1);
2241
- this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
2242
- newState.expanded = expanded;
2243
- }
2244
-
2245
- if (this.state.selected === deleteItem) {
2246
- const parts = this.state.selected.split('/');
2247
- parts.pop();
2248
- newState.selected = parts.join('/');
2249
- }
2250
-
2251
- if (!this.supportSubscribes) {
2252
- const parentFolder = this.findFirstFolder(deleteItem);
2253
- const folders: Folders = {};
2254
-
2255
- Object.keys(this.state.folders).forEach(name => {
2256
- if (name !== parentFolder && !name.startsWith(`${parentFolder}/`)) {
2257
- folders[name] = this.state.folders[name];
2258
- }
2259
- });
2260
-
2261
- newState.folders = folders;
2262
-
2263
- this.setState(newState as FileBrowserState, () =>
2264
- setTimeout(() => {
2265
- this.browseFolders([...this.state.expanded], folders)
2266
- .then(_folders => this.setState({ folders: _folders }))
2267
- .catch(e => console.error(e));
2268
- }, 200),
2269
- );
2270
- } else {
2271
- this.setState(newState as FileBrowserState);
2272
- }
2273
- }),
2274
- );
2275
- }
2276
-
2277
- renderDeleteDialog(): React.JSX.Element | null {
2278
- if (this.state.deleteItem) {
2279
- return (
2280
- <Dialog
2281
- key="deleteDialog"
2282
- open={!0}
2283
- onClose={() => this.setState({ deleteItem: '' })}
2284
- aria-labelledby="ar_dialog_file_delete_title"
2285
- >
2286
- <DialogTitle id="ar_dialog_file_delete_title">
2287
- {this.props.t('ra_Confirm deletion of %s', this.state.deleteItem.split('/').pop() as string)}
2288
- </DialogTitle>
2289
- <DialogContent>
2290
- <DialogContentText>{this.props.t('ra_Are you sure?')}</DialogContentText>
2291
- </DialogContent>
2292
- <DialogActions>
2293
- <Button
2294
- color="grey"
2295
- variant="contained"
2296
- onClick={() => {
2297
- this.suppressDeleteConfirm = Date.now() + 60000 * 5;
2298
- this.deleteItem('');
2299
- }}
2300
- >
2301
- {this.props.t('ra_Delete (no confirm for 5 mins)')}
2302
- </Button>
2303
- <Button
2304
- variant="contained"
2305
- onClick={() => this.deleteItem('')}
2306
- color="primary"
2307
- autoFocus
2308
- >
2309
- {this.props.t('ra_Delete')}
2310
- </Button>
2311
- <Button
2312
- variant="contained"
2313
- onClick={() => this.setState({ deleteItem: '' })}
2314
- color="grey"
2315
- >
2316
- {this.props.t('ra_Cancel')}
2317
- </Button>
2318
- </DialogActions>
2319
- </Dialog>
2320
- );
2321
- }
2322
- return null;
2323
- }
2324
-
2325
- renderViewDialog(): React.JSX.Element | null {
2326
- return this.state.viewer ? (
2327
- <FileViewer
2328
- supportSubscribes={this.supportSubscribes}
2329
- key={this.state.viewer}
2330
- href={this.state.viewer}
2331
- formatEditFile={this.state.formatEditFile}
2332
- themeType={this.props.themeType}
2333
- setStateBackgroundImage={this.setStateBackgroundImage}
2334
- getStyleBackgroundImage={this.getStyleBackgroundImage}
2335
- t={this.props.t}
2336
- socket={this.props.socket}
2337
- lang={this.props.lang}
2338
- expertMode={this.state.expertMode}
2339
- onClose={() => this.setState({ viewer: '', formatEditFile: '' })}
2340
- />
2341
- ) : null;
2342
- }
2343
-
2344
- renderError(): React.JSX.Element | null {
2345
- if (this.state.errorText) {
2346
- return (
2347
- <ErrorDialog
2348
- key="errorDialog"
2349
- text={this.state.errorText}
2350
- onClose={() => this.setState({ errorText: '' })}
2351
- />
2352
- );
2353
- }
2354
- return null;
2355
- }
2356
-
2357
- // used in tabs/Files
2358
- // eslint-disable-next-line react/no-unused-class-component-methods
2359
- updateItemsAcl(info: FolderOrFileItem[]): void {
2360
- this.cacheFolders = this.cacheFolders || JSON.parse(JSON.stringify(this.state.folders));
2361
- let changed;
2362
-
2363
- info.forEach(it => {
2364
- const item = this.findItem(it.id, this.cacheFolders);
2365
- if (item && JSON.stringify(item.acl) !== JSON.stringify(it.acl)) {
2366
- item.acl = it.acl;
2367
- changed = true;
2368
- }
2369
- });
2370
- if (changed) {
2371
- if (this.cacheFoldersTimeout) {
2372
- clearTimeout(this.cacheFoldersTimeout);
2373
- }
2374
- this.cacheFoldersTimeout = setTimeout(() => {
2375
- this.cacheFoldersTimeout = null;
2376
- const folders = this.cacheFolders || {};
2377
- this.cacheFolders = null;
2378
- this.setState({ folders });
2379
- }, 200);
2380
- }
2381
- }
2382
-
2383
- changeToPath(): void {
2384
- setTimeout(() => {
2385
- if (this.state.path !== this.state.selected && (!this.lastSelect || Date.now() - this.lastSelect > 100)) {
2386
- let folder = this.state.path;
2387
- if (isFile(this.state.path)) {
2388
- folder = getParentDir(this.state.path);
2389
- }
2390
- new Promise(resolve => {
2391
- if (!this.state.folders[folder]) {
2392
- this.browseFolder(folder)
2393
- .then(folders => this.setState({ folders }, () => resolve(true)))
2394
- .catch(err =>
2395
- this.setState({
2396
- errorText:
2397
- err === NOT_FOUND
2398
- ? this.props.t('ra_Cannot find "%s"', folder)
2399
- : this.props.t('ra_Cannot read "%s"', folder),
2400
- }),
2401
- );
2402
- } else {
2403
- resolve(true);
2404
- }
2405
- })
2406
- .then(
2407
- result =>
2408
- result &&
2409
- this.setState({ selected: this.state.path, currentDir: folder, pathFocus: false }),
2410
- )
2411
- .catch(e => console.error(e));
2412
- } else if (!this.lastSelect || Date.now() - this.lastSelect > 100) {
2413
- this.setState({ pathFocus: false });
2414
- }
2415
- }, 100);
2416
- }
2417
-
2418
- renderBreadcrumb(): React.JSX.Element {
2419
- const parts = this.state.currentDir.startsWith('/')
2420
- ? this.state.currentDir.split('/')
2421
- : `/${this.state.currentDir}`.split('/');
2422
- const p: string[] = [];
2423
- return (
2424
- <Breadcrumbs style={{ paddingLeft: 8 }}>
2425
- {parts.map((part, i) => {
2426
- if (part) {
2427
- p.push(part);
2428
- }
2429
- const path = p.join('/');
2430
- if (i < parts.length - 1) {
2431
- return (
2432
- <Box
2433
- component="div"
2434
- key={`${this.state.selected}_${i}`}
2435
- sx={styles.pathDivBreadcrumbDir}
2436
- onClick={e => this.changeFolder(e, path || '/')}
2437
- >
2438
- {part || this.props.t('ra_Root')}
2439
- </Box>
2440
- );
2441
- }
2442
-
2443
- return (
2444
- <div
2445
- style={styles.pathDivBreadcrumbSelected}
2446
- key={`${this.state.selected}_${i}`}
2447
- onClick={() => this.setState({ pathFocus: true })}
2448
- >
2449
- {part}
2450
- </div>
2451
- );
2452
- })}
2453
- </Breadcrumbs>
2454
- );
2455
- }
2456
-
2457
- renderPath(): React.JSX.Element {
2458
- return (
2459
- <Box
2460
- component="div"
2461
- key="path"
2462
- sx={styles.pathDiv}
2463
- >
2464
- {this.state.pathFocus ? (
2465
- <Input
2466
- value={this.state.path}
2467
- onKeyDown={e => {
2468
- if (e.key === 'Enter') {
2469
- this.changeToPath();
2470
- } else if (e.key === 'Escape') {
2471
- this.setState({ pathFocus: false });
2472
- }
2473
- }}
2474
- endAdornment={
2475
- <IconButton
2476
- size="small"
2477
- onClick={() => this.changeToPath()}
2478
- >
2479
- <EnterIcon />
2480
- </IconButton>
2481
- }
2482
- onBlur={() => this.changeToPath()}
2483
- onChange={e => this.setState({ path: e.target.value })}
2484
- style={styles.pathDivInput}
2485
- />
2486
- ) : (
2487
- this.renderBreadcrumb()
2488
- )}
2489
- </Box>
2490
- );
2491
- }
2492
-
2493
- render(): React.JSX.Element {
2494
- if (!this.props.ready) {
2495
- return <LinearProgress />;
2496
- }
2497
-
2498
- if (this.state.loadAllFolders && !this.foldersLoading) {
2499
- this.foldersLoading = true;
2500
- setTimeout(() => {
2501
- this.setState({ loadAllFolders: false, folders: {} }, () => {
2502
- this.foldersLoading = false;
2503
- this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
2504
- });
2505
- }, 300);
2506
- }
2507
-
2508
- return (
2509
- <div
2510
- style={{ ...styles.root, ...this.props.style }}
2511
- className={this.props.className}
2512
- >
2513
- {this.props.showToolbar ? this.renderToolbar() : null}
2514
- {this.state.viewType === TILE ? this.renderPath() : null}
2515
- <div
2516
- style={{
2517
- ...styles.filesDiv,
2518
- ...styles[`filesDiv${this.state.viewType}`],
2519
- }}
2520
- onClick={e => {
2521
- if (this.state.viewType !== TABLE) {
2522
- if (this.state.selected !== (this.state.currentDir || '/')) {
2523
- this.changeFolder(e, this.state.currentDir || '/');
2524
- } else {
2525
- e.stopPropagation();
2526
- }
2527
- }
2528
- }}
2529
- >
2530
- {this.state.viewType === TABLE
2531
- ? this.renderItems('/')
2532
- : this.renderItems(this.state.currentDir || '/')}
2533
- {this.state.viewType !== TABLE ? (
2534
- <div style={styles.filesDivHint}>{this.props.t('ra_select_folder_hint')}</div>
2535
- ) : null}
2536
- </div>
2537
- {this.props.allowUpload ? this.renderInputDialog() : null}
2538
- {this.props.allowUpload ? this.renderUpload() : null}
2539
- {this.props.allowDelete ? this.renderDeleteDialog() : null}
2540
- {this.props.allowView ? this.renderViewDialog() : null}
2541
- {this.state.modalEditOfAccess && this.props.modalEditOfAccessControl
2542
- ? this.props.modalEditOfAccessControl(this)
2543
- : null}
2544
- {this.renderError()}
2545
- </div>
2546
- );
2547
- }
2548
- }
2549
-
2550
- export default withWidth()(FileBrowserClass);