@iobroker/adapter-react-v5 7.0.2 → 7.1.0

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 (307) hide show
  1. package/Components/404.d.ts +3 -2
  2. package/Components/404.js +3 -2
  3. package/Components/ColorPicker.d.ts +22 -8
  4. package/Components/ColorPicker.js +34 -17
  5. package/Components/ComplexCron.js +24 -24
  6. package/Components/CopyToClipboard.d.ts +10 -1
  7. package/Components/CopyToClipboard.js +17 -8
  8. package/Components/CustomModal.d.ts +1 -1
  9. package/Components/CustomModal.js +8 -8
  10. package/Components/FileBrowser.d.ts +11 -11
  11. package/Components/FileBrowser.js +135 -152
  12. package/Components/FileViewer.js +34 -23
  13. package/Components/Icon.d.ts +16 -2
  14. package/Components/Icon.js +19 -8
  15. package/Components/IconPicker.js +10 -14
  16. package/Components/IconSelector.d.ts +1 -1
  17. package/Components/IconSelector.js +64 -74
  18. package/Components/Image.d.ts +8 -4
  19. package/Components/Image.js +13 -32
  20. package/Components/Loader.d.ts +2 -2
  21. package/Components/Loader.js +21 -18
  22. package/Components/Loaders/MV.d.ts +6 -1
  23. package/Components/Loaders/MV.js +23 -7
  24. package/Components/Loaders/PT.d.ts +7 -2
  25. package/Components/Loaders/PT.js +20 -7
  26. package/Components/Loaders/Vendor.d.ts +2 -2
  27. package/Components/Loaders/Vendor.js +15 -7
  28. package/Components/Logo.js +16 -18
  29. package/Components/MDUtils.d.ts +1 -1
  30. package/Components/MDUtils.js +8 -4
  31. package/Components/ObjectBrowser.d.ts +40 -39
  32. package/Components/ObjectBrowser.js +550 -435
  33. package/Components/Router.d.ts +1 -3
  34. package/Components/Router.js +3 -1
  35. package/Components/SaveCloseButtons.d.ts +3 -3
  36. package/Components/SaveCloseButtons.js +3 -3
  37. package/Components/Schedule.d.ts +15 -15
  38. package/Components/Schedule.js +177 -154
  39. package/Components/SelectWithIcon.d.ts +2 -2
  40. package/Components/SelectWithIcon.js +45 -34
  41. package/Components/SimpleCron/index.js +83 -43
  42. package/Components/TabContainer.js +2 -2
  43. package/Components/TabContent.js +1 -1
  44. package/Components/TabHeader.js +1 -1
  45. package/Components/TableResize.d.ts +2 -2
  46. package/Components/TableResize.js +5 -5
  47. package/Components/TextWithIcon.d.ts +1 -1
  48. package/Components/TextWithIcon.js +10 -8
  49. package/Components/ToggleThemeMenu.d.ts +2 -2
  50. package/Components/ToggleThemeMenu.js +3 -3
  51. package/Components/TreeTable.d.ts +18 -18
  52. package/Components/TreeTable.js +76 -72
  53. package/Components/UploadImage.d.ts +2 -2
  54. package/Components/UploadImage.js +25 -21
  55. package/Components/Utils.d.ts +42 -22
  56. package/Components/Utils.js +66 -65
  57. package/Components/withWidth.d.ts +2 -2
  58. package/Components/withWidth.js +10 -6
  59. package/Dialogs/ComplexCron.d.ts +2 -2
  60. package/Dialogs/ComplexCron.js +3 -3
  61. package/Dialogs/Confirm.d.ts +4 -4
  62. package/Dialogs/Confirm.js +18 -8
  63. package/Dialogs/Cron.d.ts +3 -3
  64. package/Dialogs/Cron.js +21 -17
  65. package/Dialogs/Error.d.ts +3 -3
  66. package/Dialogs/Error.js +6 -4
  67. package/Dialogs/Message.d.ts +3 -3
  68. package/Dialogs/Message.js +6 -4
  69. package/Dialogs/SelectFile.d.ts +4 -4
  70. package/Dialogs/SelectFile.js +6 -4
  71. package/Dialogs/SelectID.d.ts +12 -10
  72. package/Dialogs/SelectID.js +12 -8
  73. package/Dialogs/SimpleCron.d.ts +2 -2
  74. package/Dialogs/SimpleCron.js +2 -2
  75. package/Dialogs/TextInput.d.ts +2 -2
  76. package/Dialogs/TextInput.js +3 -3
  77. package/GenericApp.d.ts +19 -13
  78. package/GenericApp.js +128 -85
  79. package/LegacyConnection.d.ts +240 -248
  80. package/LegacyConnection.js +500 -525
  81. package/README.md +1234 -1170
  82. package/Theme.d.ts +1 -1
  83. package/Theme.js +9 -12
  84. package/assets/devices.json +1 -0
  85. package/assets/rooms.json +1 -0
  86. package/craco-module-federation.js +3 -12
  87. package/i18n/de.json +434 -434
  88. package/i18n/en.json +434 -434
  89. package/i18n/es.json +434 -434
  90. package/i18n/fr.json +434 -434
  91. package/i18n/it.json +434 -434
  92. package/i18n/nl.json +434 -434
  93. package/i18n/pl.json +434 -434
  94. package/i18n/pt.json +434 -434
  95. package/i18n/ru.json +434 -434
  96. package/i18n/uk.json +434 -434
  97. package/i18n/zh-cn.json +434 -434
  98. package/i18n.d.ts +26 -19
  99. package/i18n.js +28 -22
  100. package/icons/IconAdapter.js +2 -2
  101. package/icons/IconAlias.js +2 -2
  102. package/icons/IconChannel.js +2 -2
  103. package/icons/IconClearFilter.js +2 -2
  104. package/icons/IconClosed.js +2 -2
  105. package/icons/IconCopy.js +2 -2
  106. package/icons/IconDevice.js +2 -2
  107. package/icons/IconDocument.js +2 -2
  108. package/icons/IconDocumentReadOnly.js +2 -2
  109. package/icons/IconExpert.js +2 -2
  110. package/icons/IconFx.js +2 -2
  111. package/icons/IconInstance.js +2 -2
  112. package/icons/IconLogout.js +2 -2
  113. package/icons/IconNoIcon.js +2 -2
  114. package/icons/IconOpen.d.ts +2 -2
  115. package/icons/IconOpen.js +2 -2
  116. package/icons/IconProps.d.ts +4 -3
  117. package/icons/IconState.d.ts +2 -2
  118. package/icons/IconState.js +2 -2
  119. package/index.css +3 -2
  120. package/package.json +1 -1
  121. package/src/Components/404.tsx +32 -31
  122. package/src/Components/ColorPicker.tsx +142 -114
  123. package/src/Components/ComplexCron.tsx +174 -137
  124. package/src/Components/CopyToClipboard.tsx +22 -9
  125. package/src/Components/CustomModal.tsx +76 -69
  126. package/src/Components/FileBrowser.tsx +959 -852
  127. package/src/Components/FileViewer.tsx +146 -127
  128. package/src/Components/Icon.tsx +80 -52
  129. package/src/Components/IconPicker.tsx +83 -67
  130. package/src/Components/IconSelector.tsx +159 -141
  131. package/src/Components/Image.tsx +43 -26
  132. package/src/Components/Loader.tsx +56 -32
  133. package/src/Components/Logo.tsx +62 -52
  134. package/src/Components/MDUtils.tsx +10 -6
  135. package/src/Components/ObjectBrowser.tsx +3198 -2478
  136. package/src/Components/Router.tsx +11 -11
  137. package/src/Components/SaveCloseButtons.tsx +43 -39
  138. package/src/Components/Schedule.tsx +1091 -853
  139. package/src/Components/SelectWithIcon.tsx +135 -93
  140. package/src/Components/TabContainer.tsx +21 -19
  141. package/src/Components/TabContent.tsx +13 -12
  142. package/src/Components/TabHeader.tsx +10 -9
  143. package/src/Components/TableResize.tsx +52 -37
  144. package/src/Components/TextWithIcon.tsx +30 -19
  145. package/src/Components/ToggleThemeMenu.tsx +31 -13
  146. package/src/Components/TreeTable.tsx +468 -385
  147. package/src/Components/UploadImage.tsx +153 -121
  148. package/src/Components/Utils.tsx +135 -127
  149. package/src/Components/loader.css +40 -31
  150. package/src/Components/withWidth.tsx +23 -12
  151. package/src/Connection.tsx +1 -3
  152. package/src/Dialogs/ComplexCron.tsx +55 -61
  153. package/src/Dialogs/Confirm.tsx +88 -65
  154. package/src/Dialogs/Cron.tsx +122 -112
  155. package/src/Dialogs/Error.tsx +37 -42
  156. package/src/Dialogs/Message.tsx +39 -37
  157. package/src/Dialogs/SelectFile.tsx +95 -85
  158. package/src/Dialogs/SelectID.tsx +141 -129
  159. package/src/Dialogs/SimpleCron.tsx +44 -44
  160. package/src/Dialogs/TextInput.tsx +60 -68
  161. package/src/GenericApp.tsx +342 -242
  162. package/src/LegacyConnection.tsx +972 -842
  163. package/src/Prompt.tsx +3 -1
  164. package/src/Theme.tsx +19 -26
  165. package/src/icons/IconAdapter.tsx +16 -14
  166. package/src/icons/IconAlias.tsx +16 -14
  167. package/src/icons/IconChannel.tsx +55 -16
  168. package/src/icons/IconClearFilter.tsx +17 -15
  169. package/src/icons/IconClosed.tsx +16 -11
  170. package/src/icons/IconCopy.tsx +16 -11
  171. package/src/icons/IconDevice.tsx +121 -22
  172. package/src/icons/IconDocument.tsx +16 -11
  173. package/src/icons/IconDocumentReadOnly.tsx +21 -12
  174. package/src/icons/IconExpert.tsx +20 -12
  175. package/src/icons/IconFx.tsx +16 -14
  176. package/src/icons/IconInstance.tsx +16 -14
  177. package/src/icons/IconLogout.tsx +20 -18
  178. package/src/icons/IconNoIcon.tsx +16 -14
  179. package/src/icons/IconOpen.tsx +17 -12
  180. package/src/icons/IconProps.tsx +4 -3
  181. package/src/icons/IconState.tsx +34 -13
  182. package/src/index.css +3 -2
  183. package/tasks.js +91 -0
  184. package/types.d.ts +141 -0
  185. package/Components/Loaders/PT.css +0 -109
  186. package/Components/Loaders/Vendor.css +0 -13
  187. package/Components/loader.css +0 -222
  188. package/Components/types.d.ts +0 -82
  189. package/assets/devices/Alarm Systems.svg +0 -19
  190. package/assets/devices/Amplifier.svg +0 -22
  191. package/assets/devices/Awnings.svg +0 -5
  192. package/assets/devices/Battery Status.svg +0 -5
  193. package/assets/devices/Ceiling Spotlights.svg +0 -16
  194. package/assets/devices/Chandelier.svg +0 -7
  195. package/assets/devices/Climate.svg +0 -12
  196. package/assets/devices/Coffee Makers.svg +0 -6
  197. package/assets/devices/Cold Water.svg +0 -31
  198. package/assets/devices/Computer.svg +0 -21
  199. package/assets/devices/Consumption.svg +0 -8
  200. package/assets/devices/Curtains.svg +0 -43
  201. package/assets/devices/Dishwashers.svg +0 -12
  202. package/assets/devices/Doors.svg +0 -6
  203. package/assets/devices/Doorstep.svg +0 -35
  204. package/assets/devices/Dryer.svg +0 -14
  205. package/assets/devices/Fan.svg +0 -20
  206. package/assets/devices/Floor Lamps.svg +0 -5
  207. package/assets/devices/Garage Doors.svg +0 -9
  208. package/assets/devices/Gates.svg +0 -32
  209. package/assets/devices/Hairdryer.svg +0 -23
  210. package/assets/devices/Handle.svg +0 -6
  211. package/assets/devices/Hanging Lamps.svg +0 -9
  212. package/assets/devices/Heater.svg +0 -44
  213. package/assets/devices/Hoods.svg +0 -12
  214. package/assets/devices/Hot Water.svg +0 -10
  215. package/assets/devices/Humidity.svg +0 -41
  216. package/assets/devices/Iron.svg +0 -5
  217. package/assets/devices/Irrigation.svg +0 -23
  218. package/assets/devices/Led Strip.svg +0 -31
  219. package/assets/devices/Light.svg +0 -30
  220. package/assets/devices/Lightings.svg +0 -46
  221. package/assets/devices/Lock.svg +0 -19
  222. package/assets/devices/Louvre.svg +0 -7
  223. package/assets/devices/Mowing Machine.svg +0 -9
  224. package/assets/devices/Music.svg +0 -13
  225. package/assets/devices/Outdoor Blinds.svg +0 -7
  226. package/assets/devices/People.svg +0 -19
  227. package/assets/devices/Pool.svg +0 -8
  228. package/assets/devices/Power Consumption.svg +0 -13
  229. package/assets/devices/Printer.svg +0 -10
  230. package/assets/devices/Pump.svg +0 -10
  231. package/assets/devices/Receiver.svg +0 -19
  232. package/assets/devices/Sconces.svg +0 -10
  233. package/assets/devices/Security.svg +0 -34
  234. package/assets/devices/Shading.svg +0 -5
  235. package/assets/devices/Shutters.svg +0 -11
  236. package/assets/devices/SmokeDetector.svg +0 -13
  237. package/assets/devices/Sockets.svg +0 -13
  238. package/assets/devices/Speaker.svg +0 -35
  239. package/assets/devices/Stove.svg +0 -12
  240. package/assets/devices/Table Lamps.svg +0 -12
  241. package/assets/devices/Temperature Sensors.svg +0 -28
  242. package/assets/devices/Tv.svg +0 -8
  243. package/assets/devices/Vacuum Cleaner.svg +0 -16
  244. package/assets/devices/Ventilation.svg +0 -12
  245. package/assets/devices/Washing Machines.svg +0 -16
  246. package/assets/devices/Water Consumption.svg +0 -6
  247. package/assets/devices/Water Heater.svg +0 -8
  248. package/assets/devices/Water.svg +0 -40
  249. package/assets/devices/Weather.svg +0 -28
  250. package/assets/devices/Window.svg +0 -8
  251. package/assets/rooms/Anteroom.svg +0 -53
  252. package/assets/rooms/Attic.svg +0 -21
  253. package/assets/rooms/Balcony.svg +0 -13
  254. package/assets/rooms/Barn.svg +0 -6
  255. package/assets/rooms/Basement.svg +0 -5
  256. package/assets/rooms/Bathroom.svg +0 -38
  257. package/assets/rooms/Bedroom.svg +0 -5
  258. package/assets/rooms/Boiler Room.svg +0 -13
  259. package/assets/rooms/Carport.svg +0 -17
  260. package/assets/rooms/Cellar.svg +0 -89
  261. package/assets/rooms/Chamber.svg +0 -9
  262. package/assets/rooms/Corridor.svg +0 -53
  263. package/assets/rooms/Dining Area.svg +0 -37
  264. package/assets/rooms/Dining Room.svg +0 -37
  265. package/assets/rooms/Dining.svg +0 -37
  266. package/assets/rooms/Dressing Room.svg +0 -5
  267. package/assets/rooms/Driveway.svg +0 -15
  268. package/assets/rooms/Entrance.svg +0 -44
  269. package/assets/rooms/Equipment Room.svg +0 -15
  270. package/assets/rooms/Front Yard.svg +0 -64
  271. package/assets/rooms/Gallery.svg +0 -14
  272. package/assets/rooms/Garage.svg +0 -20
  273. package/assets/rooms/Garden.svg +0 -13
  274. package/assets/rooms/Ground Floor.svg +0 -95
  275. package/assets/rooms/Guest Bathroom.svg +0 -33
  276. package/assets/rooms/Guest Room.svg +0 -5
  277. package/assets/rooms/Gym.svg +0 -5
  278. package/assets/rooms/Hall.svg +0 -19
  279. package/assets/rooms/Home Theater.svg +0 -8
  280. package/assets/rooms/Kitchen.svg +0 -18
  281. package/assets/rooms/Laundry Room.svg +0 -12
  282. package/assets/rooms/Living Area.svg +0 -11
  283. package/assets/rooms/Living Room.svg +0 -10
  284. package/assets/rooms/Locker Room.svg +0 -17
  285. package/assets/rooms/Nursery.svg +0 -5
  286. package/assets/rooms/Office.svg +0 -8
  287. package/assets/rooms/Outdoors.svg +0 -7
  288. package/assets/rooms/Playroom.svg +0 -6
  289. package/assets/rooms/Pool.svg +0 -8
  290. package/assets/rooms/Rear Wall.svg +0 -30
  291. package/assets/rooms/Second Floor.svg +0 -95
  292. package/assets/rooms/Shed.svg +0 -16
  293. package/assets/rooms/Sleeping Area.svg +0 -22
  294. package/assets/rooms/Stairway.svg +0 -5
  295. package/assets/rooms/Stairwell.svg +0 -15
  296. package/assets/rooms/Storeroom.svg +0 -5
  297. package/assets/rooms/Summer House.svg +0 -27
  298. package/assets/rooms/Swimming Pool.svg +0 -21
  299. package/assets/rooms/Terrace.svg +0 -7
  300. package/assets/rooms/Toilet.svg +0 -10
  301. package/assets/rooms/Upstairs.svg +0 -6
  302. package/assets/rooms/Wardrobe.svg +0 -60
  303. package/assets/rooms/Washroom.svg +0 -20
  304. package/assets/rooms/Wc.svg +0 -10
  305. package/assets/rooms/Windscreen.svg +0 -60
  306. package/assets/rooms/Workshop.svg +0 -23
  307. package/assets/rooms/Workspace.svg +0 -8
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * MIT License
5
5
  *
6
- * */
6
+ */
7
7
  import React, { Component } from 'react';
8
8
  import Dropzone from 'react-dropzone';
9
9
 
@@ -71,17 +71,14 @@ import IconNoIcon from '../icons/IconNoIcon';
71
71
  import Icon from './Icon';
72
72
 
73
73
  import withWidth from './withWidth';
74
- import {
75
- ThemeName, ThemeType,
76
- Translate, IobTheme,
77
- } from '../types';
74
+ import type { ThemeName, ThemeType, Translate, IobTheme } from '../types';
78
75
 
79
76
  import FileViewer, { EXTENSIONS } from './FileViewer';
80
77
 
81
- const ROW_HEIGHT = 32;
78
+ const ROW_HEIGHT = 32;
82
79
  const BUTTON_WIDTH = 32;
83
- const TILE_HEIGHT = 120;
84
- const TILE_WIDTH = 64;
80
+ const TILE_HEIGHT = 120;
81
+ const TILE_WIDTH = 64;
85
82
 
86
83
  const NOT_FOUND = 'Not found';
87
84
 
@@ -268,9 +265,7 @@ const styles: Record<string, any> = {
268
265
  width: 30,
269
266
  height: 30,
270
267
  },
271
- itemFolderTable: {
272
-
273
- },
268
+ itemFolderTable: {},
274
269
  itemFolderTemp: {
275
270
  opacity: 0.4,
276
271
  },
@@ -505,7 +500,6 @@ export interface FileBrowserProps {
505
500
 
506
501
  restrictToFolder?: string;
507
502
 
508
- // eslint-disable-next-line no-use-before-define
509
503
  modalEditOfAccessControl?: (obj: FileBrowserClass) => React.JSX.Element | null;
510
504
 
511
505
  allowNonRestricted?: boolean;
@@ -534,9 +528,9 @@ export interface FolderOrFileItem {
534
528
 
535
529
  export type Folders = Record<string, FolderOrFileItem[]>;
536
530
 
537
- function sortFolders(a: FolderOrFileItem, b: FolderOrFileItem) {
531
+ function sortFolders(a: FolderOrFileItem, b: FolderOrFileItem): number {
538
532
  if (a.folder && b.folder) {
539
- return a.name > b.name ? 1 : (a.name < b.name ? -1 : 0);
533
+ return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
540
534
  }
541
535
  if (a.folder) {
542
536
  return -1;
@@ -544,7 +538,7 @@ function sortFolders(a: FolderOrFileItem, b: FolderOrFileItem) {
544
538
  if (b.folder) {
545
539
  return 1;
546
540
  }
547
- return a.name > b.name ? 1 : (a.name < b.name ? -1 : 0);
541
+ return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
548
542
  }
549
543
 
550
544
  interface FileBrowserState {
@@ -619,7 +613,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
619
613
  constructor(props: FileBrowserProps) {
620
614
  super(props);
621
615
 
622
- this.localStorage = ((window as any)._localStorage || window.localStorage);
616
+ this.localStorage = (window as any)._localStorage || window.localStorage;
623
617
  const expandedStr = this.localStorage.getItem('files.expanded') || '[]';
624
618
 
625
619
  if (this.props.limitPath) {
@@ -635,8 +629,12 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
635
629
  try {
636
630
  expanded = JSON.parse(expandedStr);
637
631
  if (this.limitToPath) {
638
- expanded = expanded.filter(id => id.startsWith(`${this.limitToPath}/`) ||
639
- id === this.limitToPath || this.limitToPath?.startsWith(`${id}/`));
632
+ expanded = expanded.filter(
633
+ id =>
634
+ id.startsWith(`${this.limitToPath}/`) ||
635
+ id === this.limitToPath ||
636
+ this.limitToPath?.startsWith(`${id}/`),
637
+ );
640
638
  }
641
639
  } catch {
642
640
  expanded = [];
@@ -649,10 +647,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
649
647
  viewType = TABLE;
650
648
  }
651
649
 
652
- let selected =
653
- this.props.selected ||
654
- this.localStorage.getItem('files.selected') ||
655
- USER_DATA;
650
+ let selected = this.props.selected || this.localStorage.getItem('files.selected') || USER_DATA;
656
651
 
657
652
  let currentDir: string;
658
653
 
@@ -678,8 +673,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
678
673
  currentDir = selected;
679
674
  }
680
675
  }
681
- const backgroundImage =
682
- this.localStorage.getItem('files.backgroundImage') || null;
676
+ const backgroundImage = this.localStorage.getItem('files.backgroundImage') || null;
683
677
 
684
678
  this.state = {
685
679
  viewType,
@@ -733,7 +727,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
733
727
  return null;
734
728
  }
735
729
 
736
- async loadFolders() {
730
+ async loadFolders(): Promise<void> {
737
731
  this.initialReadFinished = false;
738
732
 
739
733
  let folders = (await this.browseFolder('/')) as unknown as Folders;
@@ -768,17 +762,16 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
768
762
  });
769
763
  }
770
764
 
771
- scrollToSelected() {
765
+ scrollToSelected(): void {
772
766
  if (this.mounted) {
773
767
  const el = document.getElementById(this.state.selected);
774
768
  el?.scrollIntoView();
775
769
  }
776
770
  }
777
771
 
778
- async componentDidMount() {
772
+ async componentDidMount(): Promise<void> {
779
773
  this.mounted = true;
780
- this.loadFolders()
781
- .catch(error => console.error(`Cannot load folders: ${error}`));
774
+ this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
782
775
 
783
776
  this.supportSubscribes = await this.props.socket.checkFeatureSupported('BINARY_STATE_EVENT');
784
777
  if (this.supportSubscribes) {
@@ -786,30 +779,25 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
786
779
  }
787
780
  }
788
781
 
789
- componentWillUnmount() {
782
+ componentWillUnmount(): void {
790
783
  if (this.supportSubscribes) {
791
784
  this.props.socket.unsubscribeFiles('*', '*', this.onFileChange);
792
785
  }
793
786
  this.mounted = false;
794
787
  this.browseList = null;
795
788
  this.browseListRunning = false;
796
- Object.values(this._tempTimeout)
797
- .forEach(timer => timer && clearTimeout(timer));
789
+ Object.values(this._tempTimeout).forEach(timer => timer && clearTimeout(timer));
798
790
  this._tempTimeout = {};
799
791
  }
800
792
 
801
- browseFoldersCb(
802
- foldersList: string[],
803
- newFoldersNotNull: Folders,
804
- cb: (folders: Folders) => void,
805
- ): void {
793
+ browseFoldersCb(foldersList: string[], newFoldersNotNull: Folders, cb: (folders: Folders) => void): void {
806
794
  if (!foldersList?.length) {
807
795
  cb(newFoldersNotNull);
808
796
  } else {
809
797
  const folder = foldersList.shift();
810
798
  if (folder) {
811
- this.browseFolder(folder, newFoldersNotNull)
812
- .catch((e: Error) => console.error(`Cannot read folder ${folder}: ${e}`))
799
+ void this.browseFolder(folder, newFoldersNotNull)
800
+ .catch((e: Error) => console.error(`Cannot read folder ${folder}: ${e.message}`))
813
801
  .then(() => {
814
802
  setTimeout(() => this.browseFoldersCb(foldersList, newFoldersNotNull, cb), 0);
815
803
  });
@@ -819,10 +807,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
819
807
  }
820
808
  }
821
809
 
822
- browseFolders(
823
- foldersList: string[],
824
- _newFolders?: Folders | null,
825
- ): Promise<Folders> {
810
+ browseFolders(foldersList: string[], _newFolders?: Folders | null): Promise<Folders> {
826
811
  let newFoldersNotNull: Folders;
827
812
  if (!_newFolders) {
828
813
  newFoldersNotNull = {};
@@ -878,7 +863,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
878
863
  if (item) {
879
864
  const resolve = item.resolve;
880
865
  item.resolve = null;
881
- item.reject = null;
866
+ item.reject = null;
882
867
  item.adapter = null;
883
868
  item.relPath = null;
884
869
  if (resolve) {
@@ -949,11 +934,13 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
949
934
  if (newFoldersNotNull[folderId] && !force) {
950
935
  if (!_checkEmpty) {
951
936
  return new Promise((resolve, reject) => {
952
- Promise.all(newFoldersNotNull[folderId].filter(item => item.folder).map(item =>
953
- this.browseFolder(item.id, newFoldersNotNull, true)
954
- .catch(() => undefined)))
937
+ Promise.all(
938
+ newFoldersNotNull[folderId]
939
+ .filter(item => item.folder)
940
+ .map(item => this.browseFolder(item.id, newFoldersNotNull, true).catch(() => undefined)),
941
+ )
955
942
  .then(() => resolve(newFoldersNotNull))
956
- .catch(error => reject(error));
943
+ .catch(error => reject(new Error(error)));
957
944
  });
958
945
  }
959
946
 
@@ -986,17 +973,17 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
986
973
  }
987
974
 
988
975
  const item: FolderOrFileItem = {
989
- id: obj._id,
990
- name: obj._id,
991
- title: (obj.common && obj.common.name) || obj._id,
992
- meta: true,
993
- from: obj.from,
994
- ts: obj.ts,
995
- color: obj.common && obj.common.color,
996
- icon: obj.common && obj.common.icon,
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,
997
984
  folder: true,
998
- acl: obj.acl,
999
- level: 0,
985
+ acl: obj.acl,
986
+ level: 0,
1000
987
  };
1001
988
 
1002
989
  if (item.id === USER_DATA) {
@@ -1007,7 +994,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1007
994
  }
1008
995
  });
1009
996
 
1010
- _folders.sort((a, b) => (a.id > b.id ? 1 : (a.id < b.id ? -1 : 0)));
997
+ _folders.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
1011
998
  if (!this.limitToObjectID || this.limitToObjectID === USER_DATA) {
1012
999
  if (userData) {
1013
1000
  _folders.unshift(userData);
@@ -1017,15 +1004,16 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1017
1004
  newFoldersNotNull[folderId || '/'] = _folders;
1018
1005
 
1019
1006
  if (!_checkEmpty) {
1020
- return Promise.all(_folders.filter(item => item.folder)
1021
- .map(item =>
1022
- this.browseFolder(item.id, newFoldersNotNull, true)
1023
- .catch(() => undefined)))
1024
- .then(() => newFoldersNotNull);
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);
1025
1012
  }
1026
- } catch (e) {
1013
+ } catch (e: unknown) {
1014
+ const knownError = e as Error;
1027
1015
  if (this.initialReadFinished) {
1028
- window.alert(`Cannot read meta items: ${e}`);
1016
+ window.alert(`Cannot read meta items: ${knownError.message}`);
1029
1017
  }
1030
1018
  newFoldersNotNull[folderId || '/'] = [];
1031
1019
  }
@@ -1044,13 +1032,13 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1044
1032
 
1045
1033
  files.forEach(file => {
1046
1034
  const item: FolderOrFileItem = {
1047
- id: `${folderId}/${file.file}`,
1048
- ext: Utils.getFileExtension(file.file),
1049
- folder: file.isDir,
1050
- name: file.file,
1051
- size: file.stats?.size,
1035
+ id: `${folderId}/${file.file}`,
1036
+ ext: Utils.getFileExtension(file.file),
1037
+ folder: file.isDir,
1038
+ name: file.file,
1039
+ size: file.stats?.size,
1052
1040
  modified: file.modifiedAt,
1053
- acl: file.acl,
1041
+ acl: file.acl,
1054
1042
  level,
1055
1043
  };
1056
1044
 
@@ -1085,14 +1073,16 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1085
1073
  newFoldersNotNull[folderId] = _folders;
1086
1074
 
1087
1075
  if (!_checkEmpty) {
1088
- return Promise.all(_folders
1089
- .filter(item => item.folder)
1090
- .map(item => this.browseFolder(item.id, newFoldersNotNull, true)))
1091
- .then(() => newFoldersNotNull);
1076
+ return Promise.all(
1077
+ _folders
1078
+ .filter(item => item.folder)
1079
+ .map(item => this.browseFolder(item.id, newFoldersNotNull, true)),
1080
+ ).then(() => newFoldersNotNull);
1092
1081
  }
1093
- } catch (e) {
1082
+ } catch (e: unknown) {
1083
+ const knownError = e as Error;
1094
1084
  if (this.initialReadFinished) {
1095
- window.alert(`Cannot read ${adapter}${relPath ? `/${relPath}` : ''}: ${e}`);
1085
+ window.alert(`Cannot read ${adapter}${relPath ? `/${relPath}` : ''}: ${knownError?.message}`);
1096
1086
  }
1097
1087
  newFoldersNotNull[folderId] = [];
1098
1088
  }
@@ -1100,7 +1090,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1100
1090
  return newFoldersNotNull;
1101
1091
  }
1102
1092
 
1103
- toggleFolder(item: FolderOrFileItem, e: React.MouseEvent<Element>) {
1093
+ toggleFolder(item: FolderOrFileItem, e: React.MouseEvent<Element>): void {
1104
1094
  e?.stopPropagation();
1105
1095
  const expanded = [...this.state.expanded];
1106
1096
  const pos = expanded.indexOf(item.id);
@@ -1113,11 +1103,13 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1113
1103
  if (!item.temp) {
1114
1104
  this.browseFolder(item.id)
1115
1105
  .then(folders => this.setState({ expanded, folders }))
1116
- .catch(err => window.alert(
1117
- err === NOT_FOUND
1118
- ? this.props.t('ra_Cannot find "%s"', item.id)
1119
- : this.props.t('ra_Cannot read "%s"', item.id),
1120
- ));
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
+ );
1121
1113
  } else {
1122
1114
  this.setState({ expanded });
1123
1115
  }
@@ -1128,7 +1120,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1128
1120
  }
1129
1121
  }
1130
1122
 
1131
- onFileChange = (id: string, fileName: string, size: number | null) => {
1123
+ onFileChange = (id: string, fileName: string, size: number | null): void => {
1132
1124
  const key = `${id}/${fileName}`;
1133
1125
  const pos = key.lastIndexOf('/');
1134
1126
  const folder = key.substring(0, pos);
@@ -1143,12 +1135,13 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1143
1135
  delete this._tempTimeout[folder];
1144
1136
 
1145
1137
  this.browseFolder(folder, null, false, true)
1146
- .then(folders => this.setState({ folders }));
1138
+ .then(folders => this.setState({ folders }))
1139
+ .catch(e => console.error(`Cannot read folder: ${e.message}`));
1147
1140
  }, 300);
1148
1141
  }
1149
1142
  };
1150
1143
 
1151
- changeFolder(e: React.MouseEvent<HTMLDivElement>, folder?: string) {
1144
+ changeFolder(e: React.MouseEvent<HTMLDivElement>, folder?: string): void {
1152
1145
  if (e) {
1153
1146
  e.stopPropagation();
1154
1147
  }
@@ -1168,17 +1161,21 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1168
1161
  }
1169
1162
 
1170
1163
  if (_folder && !this.state.folders[_folder]) {
1171
- return this.browseFolder(_folder)
1172
- .then(folders => this.setState(
1173
- {
1174
- folders,
1175
- path: _folder,
1176
- currentDir: _folder,
1177
- selected: _folder,
1178
- pathFocus: false,
1179
- },
1180
- () => this.props.onSelect && this.props.onSelect(''),
1181
- ));
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;
1182
1179
  }
1183
1180
 
1184
1181
  return this.setState(
@@ -1192,7 +1189,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1192
1189
  );
1193
1190
  }
1194
1191
 
1195
- select(id: string, e?: React.MouseEvent<HTMLDivElement> | null, cb?: () => void) {
1192
+ select(id: string, e?: React.MouseEvent<HTMLDivElement> | null, cb?: () => void): void {
1196
1193
  if (e) {
1197
1194
  e.stopPropagation();
1198
1195
  }
@@ -1244,123 +1241,147 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1244
1241
  const isUserData = item.name === USER_DATA;
1245
1242
  const isSpecialData = isUserData || item.name === 'vis.0' || item.name === 'vis-2.0';
1246
1243
 
1247
- return <Box
1248
- component="div"
1249
- key={item.id}
1250
- id={item.id}
1251
- style={this.state.viewType === TABLE ? { marginLeft: padding, width: `calc(100% - ${padding}px` } : {}}
1252
- onClick={e => (this.state.viewType === TABLE ? this.select(item.id, e) : this.changeFolder(e, item.id))}
1253
- onDoubleClick={e => this.state.viewType === TABLE && this.toggleFolder(item, e)}
1254
- title={this.getText(item.title)}
1255
- className="browserItem"
1256
- sx={Utils.getStyle(
1257
- this.props.theme,
1258
- styles[`item${this.state.viewType}`],
1259
- styles[`itemFolder${this.state.viewType}`],
1260
- this.state.selected === item.id ? styles.itemSelected : {},
1261
- item.temp ? styles.itemFolderTemp : {},
1262
- )}
1263
- >
1264
- <IconEl
1265
- style={Utils.getStyle(this.props.theme, styles[`itemFolderIcon${this.state.viewType}`], isSpecialData && styles.specialFolder)}
1266
- onClick={this.state.viewType === TABLE ? (e: React.MouseEvent<Element>) => this.toggleFolder(item, e) : undefined}
1267
- />
1268
-
1244
+ return (
1269
1245
  <Box
1270
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"
1271
1254
  sx={Utils.getStyle(
1272
1255
  this.props.theme,
1273
- styles[`itemName${this.state.viewType}`],
1274
- styles[`itemNameFolder${this.state.viewType}`],
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 : {},
1275
1260
  )}
1276
1261
  >
1277
- {isUserData ? this.props.t('ra_User files') : item.name}
1278
- </Box>
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
+ />
1279
1274
 
1280
- <Box
1281
- component="div"
1282
- style={styles[`itemSize${this.state.viewType}`]}
1283
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1284
- >
1285
- {this.state.viewType === TABLE && this.state.folders[item.id]
1286
- ? this.state.folders[item.id].length
1287
- : ''}
1288
- </Box>
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>
1289
1285
 
1290
- <Box
1291
- component="div"
1292
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1293
- >
1294
- {this.state.viewType === TABLE && this.props.expertMode ? this.formatAcl(item.acl) : null}
1295
- </Box>
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>
1296
1302
 
1297
- {this.state.viewType === TABLE && this.props.expertMode ?
1298
- <Box component="div" sx={{ ...styles.itemDeleteButtonTable, display: { md: 'inline-block', sm: 'none' } }} /> : null}
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}
1299
1309
 
1300
- {this.state.viewType === TABLE && this.props.allowDownload ?
1301
- <div style={styles[`itemDownloadEmpty${this.state.viewType}`]} /> : null}
1310
+ {this.state.viewType === TABLE && this.props.allowDownload ? (
1311
+ <div style={styles[`itemDownloadEmpty${this.state.viewType}`]} />
1312
+ ) : null}
1302
1313
 
1303
- {this.state.viewType === TABLE &&
1314
+ {this.state.viewType === TABLE &&
1304
1315
  this.props.allowDelete &&
1305
1316
  this.state.folders[item.id] &&
1306
- this.state.folders[item.id].length ? <IconButton
1307
- aria-label="delete"
1308
- onClick={e => {
1309
- e.stopPropagation();
1310
- if (this.suppressDeleteConfirm > Date.now()) {
1311
- this.deleteItem(item.id);
1312
- } else {
1313
- this.setState({ deleteItem: item.id });
1314
- }
1315
- }}
1316
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1317
- size="large"
1318
- >
1319
- <DeleteIcon fontSize="small" />
1320
- </IconButton> :
1321
- this.state.viewType === TABLE && this.props.allowDelete ?
1322
- <Box component="div" sx={styles[`itemDeleteButton${this.state.viewType}`]} /> : null}
1323
- </Box>;
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
+ );
1324
1341
  }
1325
1342
 
1326
- renderBackFolder() {
1327
- return <Box
1328
- component="div"
1329
- key={this.state.currentDir}
1330
- id={this.state.currentDir}
1331
- onClick={e => this.changeFolder(e)}
1332
- title={this.props.t('ra_Back to %s', getParentDir(this.state.currentDir))}
1333
- className="browserItem"
1334
- sx={Utils.getStyle(
1335
- this.props.theme,
1336
- styles[`item${this.state.viewType}`],
1337
- styles[`itemFolder${this.state.viewType}`],
1338
- )}
1339
- >
1340
- <IconClosed style={styles[`itemFolderIcon${this.state.viewType}`]} />
1341
- <IconBack sx={styles.itemFolderIconBack} />
1342
-
1343
+ renderBackFolder(): React.JSX.Element {
1344
+ return (
1343
1345
  <Box
1344
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"
1345
1352
  sx={Utils.getStyle(
1346
1353
  this.props.theme,
1347
- styles[`itemName${this.state.viewType}`],
1348
- styles[`itemNameFolder${this.state.viewType}`],
1354
+ styles[`item${this.state.viewType}`],
1355
+ styles[`itemFolder${this.state.viewType}`],
1349
1356
  )}
1350
1357
  >
1351
- ..
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>
1352
1371
  </Box>
1353
- </Box>;
1372
+ );
1354
1373
  }
1355
1374
 
1356
- formatSize(size: number | null | undefined) {
1357
- return <div style={styles[`itemSize${this.state.viewType}`]}>
1358
- {size || size === 0 ? Utils.formatBytes(size) : ''}
1359
- </div>;
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
+ );
1360
1381
  }
1361
1382
 
1362
- formatAcl(acl: ioBroker.EvaluatedFileACL | MetaACL | undefined) {
1363
- const access: number = acl ? ((acl as ioBroker.EvaluatedFileACL).permissions || (acl as MetaACL).file) : 0;
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;
1364
1385
  let accessStr: string;
1365
1386
  if (access) {
1366
1387
  accessStr = access.toString(16).padStart(3, '0');
@@ -1368,18 +1389,24 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1368
1389
  accessStr = '';
1369
1390
  }
1370
1391
 
1371
- return <div style={styles[`itemAccess${this.state.viewType}`]}>
1372
- {this.props.modalEditOfAccessControl ? <IconButton
1373
- size="large"
1374
- onClick={() => this.setState({ modalEditOfAccess: true })}
1375
- sx={styles[`itemAclButton${this.state.viewType}`]}
1376
- >
1377
- {accessStr || '---'}
1378
- </IconButton> : accessStr || '---'}
1379
- </div>;
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
+ );
1380
1407
  }
1381
1408
 
1382
- getFileIcon(ext: string | null) {
1409
+ getFileIcon(ext: string | null): React.JSX.Element {
1383
1410
  switch (ext) {
1384
1411
  case 'json':
1385
1412
  case 'json5':
@@ -1424,14 +1451,15 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1424
1451
  }
1425
1452
  }
1426
1453
 
1427
- setStateBackgroundImage = () => {
1454
+ setStateBackgroundImage = (): void => {
1428
1455
  const array = ['light', 'dark', 'colored', 'delete'];
1429
1456
  this.setState(({ backgroundImage }) => {
1430
- if (backgroundImage && array.indexOf(backgroundImage) !== -1 && array.length - 1 !== array.indexOf(backgroundImage)) {
1431
- this.localStorage.setItem(
1432
- 'files.backgroundImage',
1433
- array[array.indexOf(backgroundImage) + 1],
1434
- );
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]);
1435
1463
  return { backgroundImage: array[array.indexOf(backgroundImage) + 1] };
1436
1464
  }
1437
1465
  this.localStorage.setItem('files.backgroundImage', array[0]);
@@ -1439,7 +1467,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1439
1467
  });
1440
1468
  };
1441
1469
 
1442
- getStyleBackgroundImage = () => {
1470
+ getStyleBackgroundImage = (): React.CSSProperties | null => {
1443
1471
  // ['light', 'dark', 'colored', 'delete']
1444
1472
  switch (this.state.backgroundImage) {
1445
1473
  case 'light':
@@ -1459,137 +1487,156 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1459
1487
  const padding = this.state.viewType === TABLE ? item.level * this.levelPadding : 0;
1460
1488
  const ext = Utils.getFileExtension(item.name);
1461
1489
 
1462
- return <Box
1463
- component="div"
1464
- key={item.id}
1465
- id={item.id}
1466
- onDoubleClick={e => {
1467
- e.stopPropagation();
1468
- if (!this.props.onSelect) {
1469
- this.setState({ viewer: this.imagePrefix + item.id, formatEditFile: ext });
1470
- } else if (
1471
- (!this.props.filterFiles || (item.ext && this.props.filterFiles.includes(item.ext))) &&
1472
- (!this.state.filterByType ||
1473
- (item.ext &&
1474
- (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(item.ext)))
1475
- ) {
1476
- this.props.onSelect(item.id, true, !!this.state.folders[item.id]);
1477
- }
1478
- }}
1479
- onClick={e => this.select(item.id, e)}
1480
- style={this.state.viewType === TABLE ? { marginLeft: padding, width: `calc(100% - ${padding}px)` } : {}}
1481
- className="browserItem"
1482
- sx={Utils.getStyle(
1483
- this.props.theme,
1484
- styles[`item${this.state.viewType}`],
1485
- styles[`itemFile${this.state.viewType}`],
1486
- this.state.selected === item.id ? styles.itemSelected : undefined,
1487
- )}
1488
- >
1489
- {ext && EXTENSIONS.images.includes(ext) ?
1490
- this.state.fileErrors.includes(item.id) ?
1491
- <IconNoIcon
1492
- style={{
1493
- ...styles[`itemImage${this.state.viewType}`],
1494
- ...this.getStyleBackgroundImage(),
1495
- ...styles[`itemNoImage${this.state.viewType}`],
1496
- }}
1497
- /> :
1498
- <Icon
1499
- onError={e => {
1500
- (e.target as HTMLImageElement).onerror = null;
1501
- const fileErrors = [...this.state.fileErrors];
1502
- if (!fileErrors.includes(item.id)) {
1503
- fileErrors.push(item.id);
1504
- this.setState({ fileErrors });
1505
- }
1506
- }}
1507
- style={{ ...styles[`itemImage${this.state.viewType}`], ...this.getStyleBackgroundImage() }}
1508
- src={this.imagePrefix + item.id}
1509
- alt={item.name}
1510
- />
1511
- :
1512
- this.getFileIcon(ext)}
1513
- <Box component="div" sx={styles[`itemName${this.state.viewType}`]}>{item.name}</Box>
1514
- <Box
1515
- component="div"
1516
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1517
- >
1518
- {this.formatSize(item.size)}
1519
- </Box>
1490
+ return (
1520
1491
  <Box
1521
1492
  component="div"
1522
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
1523
- >
1524
- {this.state.viewType === TABLE && this.props.expertMode ? this.formatAcl(item.acl) : null}
1525
- </Box>
1526
- <Box
1527
- component="div"
1528
- sx={{ display: { md: 'inline-block', sm: 'none' } }}
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
+ )}
1529
1517
  >
1530
- {this.state.viewType === TABLE && this.props.expertMode && FileBrowserClass.getEditFile(ext) ?
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 ? (
1531
1617
  <IconButton
1532
- aria-label="edit"
1618
+ aria-label="delete"
1533
1619
  onClick={e => {
1534
1620
  e.stopPropagation();
1535
- if (!this.props.onSelect) {
1536
- this.setState({ viewer: this.imagePrefix + item.id, formatEditFile: ext });
1537
- } else if (
1538
- (!this.props.filterFiles ||
1539
- (item.ext && this.props.filterFiles.includes(item.ext))) &&
1540
- (!this.state.filterByType ||
1541
- (item.ext &&
1542
- (EXTENSIONS as Record<string, string[]>)[this.state.filterByType].includes(
1543
- item.ext,
1544
- )))
1545
- ) {
1546
- this.props.onSelect(item.id, true, !!this.state.folders[item.id]);
1621
+ if (this.suppressDeleteConfirm > Date.now()) {
1622
+ this.deleteItem(item.id);
1623
+ } else {
1624
+ this.setState({ deleteItem: item.id });
1547
1625
  }
1548
1626
  }}
1549
- sx={styles.itemDeleteButtonTable}
1627
+ sx={styles[`itemDeleteButton${this.state.viewType}`]}
1550
1628
  size="large"
1551
1629
  >
1552
- <EditIcon fontSize="small" />
1630
+ <DeleteIcon fontSize="small" />
1553
1631
  </IconButton>
1554
- :
1555
- <Box component="div" sx={styles[`itemDeleteButton${this.state.viewType}`]} />}
1632
+ ) : this.state.viewType === TABLE && this.props.allowDelete ? (
1633
+ <Box
1634
+ component="div"
1635
+ sx={styles[`itemDeleteButton${this.state.viewType}`]}
1636
+ />
1637
+ ) : null}
1556
1638
  </Box>
1557
- {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
1558
- {this.state.viewType === TABLE && this.props.allowDownload ? <Box
1559
- component="a"
1560
- className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge"
1561
- sx={styles.itemDownloadButtonTable}
1562
- tabIndex={0}
1563
- download={item.id}
1564
- href={this.imagePrefix + item.id}
1565
- onClick={e => e.stopPropagation()}
1566
- >
1567
- <DownloadIcon />
1568
- </Box> : null}
1569
-
1570
- {this.state.viewType === TABLE &&
1571
- this.props.allowDelete &&
1572
- item.id !== 'vis.0/' &&
1573
- item.id !== 'vis-2.0/' &&
1574
- item.id !== USER_DATA ? <IconButton
1575
- aria-label="delete"
1576
- onClick={e => {
1577
- e.stopPropagation();
1578
- if (this.suppressDeleteConfirm > Date.now()) {
1579
- this.deleteItem(item.id);
1580
- } else {
1581
- this.setState({ deleteItem: item.id });
1582
- }
1583
- }}
1584
- sx={styles[`itemDeleteButton${this.state.viewType}`]}
1585
- size="large"
1586
- >
1587
- <DeleteIcon fontSize="small" />
1588
- </IconButton>
1589
- :
1590
- (this.state.viewType === TABLE && this.props.allowDelete ?
1591
- <Box component="div" sx={styles[`itemDeleteButton${this.state.viewType}`]} /> : null)}
1592
- </Box>;
1639
+ );
1593
1640
  }
1594
1641
 
1595
1642
  renderItems(folderId: string): React.JSX.Element | (React.JSX.Element | null)[] {
@@ -1647,240 +1694,264 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1647
1694
  return totalResult;
1648
1695
  }
1649
1696
 
1650
- return <div style={{ position: 'relative' }}>
1651
- <CircularProgress key={folderId} color="secondary" size={24} />
1652
- <div
1653
- style={{
1654
- position: 'absolute',
1655
- zIndex: 2,
1656
- top: 4,
1657
- width: 24,
1658
- textAlign: 'center',
1659
- }}
1660
- >
1661
- {this.state.queueLength}
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>
1662
1715
  </div>
1663
- </div>;
1716
+ );
1664
1717
  }
1665
1718
 
1666
- renderToolbar() {
1667
- const IconType: React.FC< { fontSize?: 'small' }> | null = this.props.showTypeSelector
1668
- ? FILE_TYPE_ICONS[this.state.filterByType || 'all'] ||
1669
- FILE_TYPE_ICONS.all
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
1670
1722
  : null;
1671
1723
 
1672
1724
  const isInFolder = this.findFirstFolder(this.state.selected);
1673
1725
 
1674
- return <Toolbar key="toolbar" variant="dense">
1675
- {this.props.allowNonRestricted && this.props.restrictToFolder ? <IconButton
1676
- edge="start"
1677
- title={
1678
- this.state.restrictToFolder
1679
- ? this.props.t('ra_Show all folders')
1680
- : this.props.t('ra_Restrict to folder')
1681
- }
1682
- style={{
1683
- ...styles.menuButton,
1684
- ...(this.state.restrictToFolder ? styles.menuButtonRestrictActive : undefined),
1685
- }}
1686
- aria-label="restricted to folder"
1687
- onClick={() =>
1688
- this.setState({
1689
- restrictToFolder:
1690
- (this.state.restrictToFolder ? '' : this.props.restrictToFolder) || '',
1691
- loadAllFolders: true,
1692
- })}
1693
- size="small"
1694
- >
1695
- <RestrictedIcon fontSize="small" />
1696
- </IconButton> : null}
1697
- {this.props.showExpertButton ? <IconButton
1698
- edge="start"
1699
- title={this.props.t('ra_Toggle expert mode')}
1700
- style={{
1701
- ...styles.menuButton,
1702
- ...(this.state.expertMode ? styles.menuButtonExpertActive : undefined),
1703
- }}
1704
- aria-label="expert mode"
1705
- onClick={() => this.setState({ expertMode: !this.state.expertMode })}
1706
- size="small"
1726
+ return (
1727
+ <Toolbar
1728
+ key="toolbar"
1729
+ variant="dense"
1707
1730
  >
1708
- <IconExpert />
1709
- </IconButton> : null}
1710
- {this.props.showViewTypeButton ? <IconButton
1711
- edge="start"
1712
- title={this.props.t('ra_Toggle view mode')}
1713
- style={styles.menuButton}
1714
- aria-label="view mode"
1715
- onClick={() => {
1716
- const viewType = this.state.viewType === TABLE ? TILE : TABLE;
1717
- this.localStorage.setItem('files.viewType', viewType);
1718
- let currentDir = this.state.selected;
1719
- if (isFile(currentDir)) {
1720
- currentDir = getParentDir(currentDir);
1721
- }
1722
- this.setState({ viewType, currentDir }, () => {
1723
- if (this.state.viewType === TABLE) {
1724
- this.scrollToSelected();
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')
1725
1738
  }
1726
- });
1727
- }}
1728
- size="small"
1729
- >
1730
- {this.state.viewType !== TABLE ? <IconList fontSize="small" /> : <IconTile fontSize="small" />}
1731
- </IconButton> : null}
1732
- <IconButton
1733
- edge="start"
1734
- title={this.props.t('ra_Hide empty folders')}
1735
- style={styles.menuButton}
1736
- color={this.state.filterEmpty ? 'secondary' : 'inherit'}
1737
- aria-label="filter empty"
1738
- onClick={() => {
1739
- this.localStorage.setItem(
1740
- 'file.empty',
1741
- this.state.filterEmpty ? 'false' : 'true',
1742
- );
1743
- this.setState({ filterEmpty: !this.state.filterEmpty });
1744
- }}
1745
- size="small"
1746
- >
1747
- <EmptyFilterIcon fontSize="small" />
1748
- </IconButton>
1749
- <IconButton
1750
- edge="start"
1751
- title={this.props.t('ra_Reload files')}
1752
- style={styles.menuButton}
1753
- color="inherit"
1754
- aria-label="reload files"
1755
- onClick={() => this.setState({ folders: {} }, () => this.loadFolders())}
1756
- size="small"
1757
- >
1758
- <RefreshIcon fontSize="small" />
1759
- </IconButton>
1760
- {this.props.allowCreateFolder ? <IconButton
1761
- edge="start"
1762
- disabled={
1763
- !this.state.selected ||
1764
- !isInFolder ||
1765
- (!!this.limitToPath &&
1766
- !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1767
- this.limitToPath !== this.state.selected)
1768
- }
1769
- title={this.props.t('ra_Create folder')}
1770
- style={styles.menuButton}
1771
- color="inherit"
1772
- aria-label="add folder"
1773
- onClick={() => this.setState({ addFolder: true })}
1774
- size="small"
1775
- >
1776
- <AddFolderIcon fontSize="small" />
1777
- </IconButton> : null}
1778
- {this.props.allowUpload ? <IconButton
1779
- edge="start"
1780
- disabled={
1781
- !this.state.selected ||
1782
- !isInFolder ||
1783
- (!!this.limitToPath &&
1784
- !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1785
- this.limitToPath !== this.state.selected)
1786
- }
1787
- title={this.props.t('ra_Upload file')}
1788
- style={styles.menuButton}
1789
- color="inherit"
1790
- aria-label="upload file"
1791
- onClick={() => this.setState({ uploadFile: true })}
1792
- size="small"
1793
- >
1794
- <UploadIcon fontSize="small" />
1795
- </IconButton> : null}
1796
- {this.props.showTypeSelector && IconType ? <Tooltip title={this.props.t('ra_Filter files')} slotProps={{ popper: { sx: styles.tooltip } }}>
1797
- <IconButton size="small" onClick={e => this.setState({ showTypesMenu: e.target as HTMLButtonElement })}>
1798
- <IconType fontSize="small" />
1799
- </IconButton>
1800
- </Tooltip> : null}
1801
- {this.state.showTypesMenu ? <Menu
1802
- open={!0}
1803
- anchorEl={this.state.showTypesMenu}
1804
- onClose={() => this.setState({ showTypesMenu: null })}
1805
- >
1806
- {Object.keys(FILE_TYPE_ICONS).map(type => {
1807
- const MyIcon: React.FC<{ fontSize?: 'small' }> = FILE_TYPE_ICONS[type];
1808
- return <MenuItem
1809
- key={type}
1810
- selected={this.state.filterByType === type}
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"
1811
1777
  onClick={() => {
1812
- if (type === 'all') {
1813
- this.localStorage.removeItem(
1814
- 'files.filterByType',
1815
- );
1816
- this.setState({ filterByType: '', showTypesMenu: null });
1817
- } else {
1818
- this.localStorage.setItem(
1819
- 'files.filterByType',
1820
- type,
1821
- );
1822
- this.setState({ filterByType: type, showTypesMenu: null });
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);
1823
1783
  }
1784
+ this.setState({ viewType, currentDir }, () => {
1785
+ if (this.state.viewType === TABLE) {
1786
+ this.scrollToSelected();
1787
+ }
1788
+ });
1824
1789
  }}
1790
+ size="small"
1825
1791
  >
1826
- <ListItemIcon>
1827
- <MyIcon fontSize="small" />
1828
- </ListItemIcon>
1829
- <ListItemText>{this.props.t(`ra_fileType_${type}`)}</ListItemText>
1830
- </MenuItem>;
1831
- })}
1832
- </Menu> : null}
1833
- <Tooltip
1834
- title={this.props.t('ra_Background image')}
1835
- slotProps={{ popper: { sx: styles.tooltip } }}
1836
- >
1792
+ {this.state.viewType !== TABLE ? <IconList fontSize="small" /> : <IconTile fontSize="small" />}
1793
+ </IconButton>
1794
+ ) : null}
1837
1795
  <IconButton
1838
- color="inherit"
1839
1796
  edge="start"
1797
+ title={this.props.t('ra_Hide empty folders')}
1840
1798
  style={styles.menuButton}
1841
- onClick={this.setStateBackgroundImage}
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
+ }}
1842
1805
  size="small"
1843
1806
  >
1844
- <Brightness5Icon fontSize="small" />
1807
+ <EmptyFilterIcon fontSize="small" />
1845
1808
  </IconButton>
1846
- </Tooltip>
1847
- {this.state.viewType !== TABLE && this.props.allowDelete ? <Tooltip
1848
- title={this.props.t('ra_Delete')}
1849
- slotProps={{ popper: { sx: styles.tooltip } }}
1850
- >
1851
- <span>
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 ? (
1852
1821
  <IconButton
1853
- aria-label="delete"
1822
+ edge="start"
1854
1823
  disabled={
1855
1824
  !this.state.selected ||
1856
- this.state.selected === 'vis.0/' ||
1857
- this.state.selected === 'vis-2.0/' ||
1858
- this.state.selected === USER_DATA
1825
+ !isInFolder ||
1826
+ (!!this.limitToPath &&
1827
+ !this.state.selected.startsWith(`${this.limitToPath}/`) &&
1828
+ this.limitToPath !== this.state.selected)
1859
1829
  }
1830
+ title={this.props.t('ra_Create folder')}
1831
+ style={styles.menuButton}
1860
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
1861
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')}
1862
1851
  style={styles.menuButton}
1863
- onClick={e => {
1864
- e.stopPropagation();
1865
- if (this.suppressDeleteConfirm > Date.now()) {
1866
- this.deleteItem(this.state.selected);
1867
- } else {
1868
- this.setState({ deleteItem: this.state.selected });
1869
- }
1870
- }}
1852
+ color="inherit"
1853
+ aria-label="upload file"
1854
+ onClick={() => this.setState({ uploadFile: true })}
1871
1855
  size="small"
1872
1856
  >
1873
- <DeleteIcon fontSize="small" />
1857
+ <UploadIcon fontSize="small" />
1874
1858
  </IconButton>
1875
- </span>
1876
- </Tooltip> : null}
1877
- </Toolbar>;
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
+ );
1878
1952
  }
1879
1953
 
1880
- findItem(
1881
- id: string,
1882
- folders?: Folders | null,
1883
- ) {
1954
+ findItem(id: string, folders?: Folders | null): null | FolderOrFileItem {
1884
1955
  folders = folders || this.state.folders;
1885
1956
  if (!folders) {
1886
1957
  return null;
@@ -1891,10 +1962,10 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1891
1962
  if (!folders[parentFolder]) {
1892
1963
  return null;
1893
1964
  }
1894
- return folders[parentFolder].find(item => item.id === id);
1965
+ return folders[parentFolder].find(item => item.id === id) || null;
1895
1966
  }
1896
1967
 
1897
- renderInputDialog() {
1968
+ renderInputDialog(): React.JSX.Element | null {
1898
1969
  if (this.state.addFolder) {
1899
1970
  const parentFolder = this.findFirstFolder(this.state.selected);
1900
1971
 
@@ -1903,59 +1974,59 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1903
1974
  return null;
1904
1975
  }
1905
1976
 
1906
- return <TextInputDialog
1907
- key="inputDialog"
1908
- applyText={this.props.t('ra_Create')}
1909
- cancelText={this.props.t('ra_Cancel')}
1910
- titleText={this.props.t('ra_Create new folder in %s', this.state.selected)}
1911
- promptText={this.props.t(
1912
- 'ra_If no file will be created in the folder, it will disappear after the browser closed',
1913
- )}
1914
- labelText={this.props.t('ra_Folder name')}
1915
- verify={(text: string) =>
1916
- (this.state.folders[parentFolder].find(item => item.name === text)
1917
- ? ''
1918
- : this.props.t('ra_Duplicate name'))}
1919
- onClose={(name: string | null) => {
1920
- if (name) {
1921
- const folders: Folders = {};
1922
- Object.keys(this.state.folders).forEach(
1923
- folder => (folders[folder] = this.state.folders[folder]),
1924
- );
1925
- const parent = this.findItem(parentFolder);
1926
- const id = `${parentFolder}/${name}`;
1927
- folders[parentFolder].push({
1928
- id,
1929
- level: (parent?.level || 0) + 1,
1930
- name,
1931
- folder: true,
1932
- temp: true,
1933
- });
1934
-
1935
- folders[parentFolder].sort(sortFolders);
1936
-
1937
- folders[id] = [];
1938
- const expanded = [...this.state.expanded];
1939
- if (!expanded.includes(parentFolder)) {
1940
- expanded.push(parentFolder);
1941
- expanded.sort();
1942
- }
1943
- this.localStorage.setItem(
1944
- 'files.expanded',
1945
- JSON.stringify(expanded),
1946
- );
1947
- this.setState({ addFolder: false, folders, expanded }, () => this.select(id));
1948
- } else {
1949
- this.setState({ addFolder: false });
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')
1950
1991
  }
1951
- }}
1952
- replace={(text: string) => text.replace(/[^-_\w]/, '_')}
1953
- />;
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
+ );
1954
2025
  }
1955
2026
  return null;
1956
2027
  }
1957
2028
 
1958
- componentDidUpdate(/* prevProps , prevState, snapshot */) {
2029
+ componentDidUpdate(/* prevProps , prevState, snapshot */): void {
1959
2030
  if (this.setOpacityTimer) {
1960
2031
  clearTimeout(this.setOpacityTimer);
1961
2032
  }
@@ -1968,7 +2039,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1968
2039
  }, 100);
1969
2040
  }
1970
2041
 
1971
- findFirstFolder(id: string) {
2042
+ findFirstFolder(id: string): string | null {
1972
2043
  let parentFolder = id;
1973
2044
  const item = this.findItem(parentFolder);
1974
2045
  // find folder
@@ -1997,12 +2068,13 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
1997
2068
  const adapterName = parts.shift();
1998
2069
  try {
1999
2070
  await this.props.socket.writeFile64(adapterName || '', parts.join('/'), data);
2000
- } catch (e) {
2001
- window.alert(`Cannot write file: ${e}`);
2071
+ } catch (e: unknown) {
2072
+ const knownError = e as Error;
2073
+ window.alert(`Cannot write file: ${knownError?.message}`);
2002
2074
  }
2003
2075
  }
2004
2076
 
2005
- renderUpload() {
2077
+ renderUpload(): React.JSX.Element[] | null {
2006
2078
  if (this.state.uploadFile) {
2007
2079
  return [
2008
2080
  <Fab
@@ -2034,46 +2106,47 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2034
2106
  } else {
2035
2107
  const id = `${parentFolder}/${file.name}`;
2036
2108
 
2037
- this.uploadFile(id, reader.result as string)
2038
- .then(() => {
2039
- if (!--count) {
2040
- this.setState({ uploadFile: false }, () => {
2041
- if (this.supportSubscribes) {
2042
- // open current folder
2043
- const expanded = [...this.state.expanded];
2044
- if (!expanded.includes(parentFolder)) {
2045
- expanded.push(parentFolder);
2046
- expanded.sort();
2047
- this.localStorage.setItem(
2048
- 'files.expanded',
2049
- JSON.stringify(expanded),
2050
- );
2051
- }
2052
- this.setState({ expanded }, () => this.select(id));
2053
- } else {
2054
- setTimeout(
2055
- () =>
2056
- this.browseFolder(parentFolder, null, false, true)
2057
- .then(folders => {
2058
- // open current folder
2059
- const expanded = [...this.state.expanded];
2060
- if (!expanded.includes(parentFolder)) {
2061
- expanded.push(parentFolder);
2062
- expanded.sort();
2063
- this.localStorage.setItem(
2064
- 'files.expanded',
2065
- JSON.stringify(expanded),
2066
- );
2067
- }
2068
- this.setState({ folders, expanded }, () =>
2069
- this.select(id));
2070
- }),
2071
- 500,
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),
2072
2121
  );
2073
2122
  }
2074
- });
2075
- }
2076
- });
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
+ });
2077
2150
  }
2078
2151
  };
2079
2152
 
@@ -2081,27 +2154,32 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2081
2154
  });
2082
2155
  }}
2083
2156
  >
2084
- {({ getRootProps, getInputProps }) => <div
2085
- style={{
2086
- ...styles.uploadDiv,
2087
- ...(this.state.uploadFile === 'dragging' ? styles.uploadDivDragging : undefined),
2088
- }}
2089
- {...getRootProps()}
2090
- >
2091
- <input {...getInputProps()} />
2092
- <Box component="div" sx={styles.uploadCenterDiv}>
2093
- <div style={styles.uploadCenterTextAndIcon}>
2094
- <UploadIcon style={styles.uploadCenterIcon} />
2095
- <div style={styles.uploadCenterText}>
2096
- {this.state.uploadFile === 'dragging'
2097
- ? this.props.t('ra_Drop file here')
2098
- : this.props.t(
2099
- 'ra_Place your files here or click here to open the browse dialog',
2100
- )}
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>
2101
2179
  </div>
2102
- </div>
2103
- </Box>
2104
- </div>}
2180
+ </Box>
2181
+ </div>
2182
+ )}
2105
2183
  </Dropzone>,
2106
2184
  ];
2107
2185
  }
@@ -2115,30 +2193,29 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2115
2193
  this.state.folders[id]
2116
2194
  ? Promise.all(this.state.folders[id].map(_item => this.deleteRecursive(_item.id)))
2117
2195
  : Promise.resolve()
2118
- )
2119
- .then(() => {
2120
- // If it is a folder of second level
2121
- if (item.level >= 1) {
2122
- const parts = id.split('/');
2123
- const adapter = parts.shift();
2124
- this.props.socket.deleteFolder(adapter || '', parts.join('/')).then(() => {
2125
- // remove this folder
2126
- const folders = JSON.parse(JSON.stringify(this.state.folders));
2127
- delete folders[item.id];
2128
- // delete folder from parent item
2129
- const parentId = getParentDir(item.id);
2130
- const parentFolder = folders[parentId];
2131
- if (parentFolder) {
2132
- const pos = parentFolder.findIndex((f: FolderOrFileItem) => f.id === item.id);
2133
- if (pos !== -1) {
2134
- parentFolder.splice(pos, 1);
2135
- }
2136
-
2137
- this.select(parentId, null, () => this.setState({ folders }));
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);
2138
2212
  }
2139
- });
2140
- }
2141
- });
2213
+
2214
+ this.select(parentId, null, () => this.setState({ folders }));
2215
+ }
2216
+ });
2217
+ }
2218
+ });
2142
2219
  }
2143
2220
 
2144
2221
  const parts = id.split('/');
@@ -2151,128 +2228,135 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2151
2228
  return Promise.resolve();
2152
2229
  }
2153
2230
 
2154
- deleteItem(deleteItem: string) {
2231
+ deleteItem(deleteItem: string): void {
2155
2232
  deleteItem = deleteItem || this.state.deleteItem;
2156
2233
 
2157
2234
  this.setState({ deleteItem: '' }, () =>
2158
- this.deleteRecursive(deleteItem)
2159
- .then(() => {
2160
- const newState: Partial<FileBrowserState> = {};
2161
- const pos = this.state.expanded.indexOf(deleteItem);
2162
- if (pos !== -1) {
2163
- const expanded = [...this.state.expanded];
2164
- expanded.splice(pos, 1);
2165
- this.localStorage.setItem('files.expanded', JSON.stringify(expanded));
2166
- newState.expanded = expanded;
2167
- }
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
+ }
2168
2244
 
2169
- if (this.state.selected === deleteItem) {
2170
- const parts = this.state.selected.split('/');
2171
- parts.pop();
2172
- newState.selected = parts.join('/');
2173
- }
2245
+ if (this.state.selected === deleteItem) {
2246
+ const parts = this.state.selected.split('/');
2247
+ parts.pop();
2248
+ newState.selected = parts.join('/');
2249
+ }
2174
2250
 
2175
- if (!this.supportSubscribes) {
2176
- const parentFolder = this.findFirstFolder(deleteItem);
2177
- const folders: Folders = {};
2251
+ if (!this.supportSubscribes) {
2252
+ const parentFolder = this.findFirstFolder(deleteItem);
2253
+ const folders: Folders = {};
2178
2254
 
2179
- Object.keys(this.state.folders).forEach(name => {
2180
- if (name !== parentFolder && !name.startsWith(`${parentFolder}/`)) {
2181
- folders[name] = this.state.folders[name];
2182
- }
2183
- });
2255
+ Object.keys(this.state.folders).forEach(name => {
2256
+ if (name !== parentFolder && !name.startsWith(`${parentFolder}/`)) {
2257
+ folders[name] = this.state.folders[name];
2258
+ }
2259
+ });
2184
2260
 
2185
- newState.folders = folders;
2261
+ newState.folders = folders;
2186
2262
 
2187
- this.setState(newState as FileBrowserState, () =>
2188
- setTimeout(() => {
2189
- (this.browseFolders([...this.state.expanded], folders) as Promise<Folders>)
2190
- .then(_folders => this.setState({ folders: _folders }));
2191
- }, 200));
2192
- } else {
2193
- // @ts-expect-error fix later
2194
- this.setState(newState);
2195
- }
2196
- }));
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
+ );
2197
2275
  }
2198
2276
 
2199
- renderDeleteDialog() {
2277
+ renderDeleteDialog(): React.JSX.Element | null {
2200
2278
  if (this.state.deleteItem) {
2201
- return <Dialog
2202
- key="deleteDialog"
2203
- open={!0}
2204
- onClose={() => this.setState({ deleteItem: '' })}
2205
- aria-labelledby="ar_dialog_file_delete_title"
2206
- >
2207
- <DialogTitle id="ar_dialog_file_delete_title">
2208
- {this.props.t('ra_Confirm deletion of %s', this.state.deleteItem.split('/').pop() as string)}
2209
- </DialogTitle>
2210
- <DialogContent>
2211
- <DialogContentText>{this.props.t('ra_Are you sure?')}</DialogContentText>
2212
- </DialogContent>
2213
- <DialogActions>
2214
- <Button
2215
- color="grey"
2216
- variant="contained"
2217
- onClick={() => {
2218
- this.suppressDeleteConfirm = Date.now() + 60000 * 5;
2219
- this.deleteItem('');
2220
- }}
2221
- >
2222
- {this.props.t('ra_Delete (no confirm for 5 mins)')}
2223
- </Button>
2224
- <Button
2225
- variant="contained"
2226
- onClick={() => this.deleteItem('')}
2227
- color="primary"
2228
- autoFocus
2229
- >
2230
- {this.props.t('ra_Delete')}
2231
- </Button>
2232
- <Button
2233
- variant="contained"
2234
- onClick={() => this.setState({ deleteItem: '' })}
2235
- color="grey"
2236
- >
2237
- {this.props.t('ra_Cancel')}
2238
- </Button>
2239
- </DialogActions>
2240
- </Dialog>;
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
+ );
2241
2321
  }
2242
- return false;
2322
+ return null;
2243
2323
  }
2244
2324
 
2245
- renderViewDialog() {
2246
- return this.state.viewer ? <FileViewer
2247
- supportSubscribes={this.supportSubscribes}
2248
- key={this.state.viewer}
2249
- href={this.state.viewer}
2250
- formatEditFile={this.state.formatEditFile}
2251
- themeType={this.props.themeType}
2252
- setStateBackgroundImage={this.setStateBackgroundImage}
2253
- getStyleBackgroundImage={this.getStyleBackgroundImage}
2254
- t={this.props.t}
2255
- socket={this.props.socket}
2256
- lang={this.props.lang}
2257
- expertMode={this.state.expertMode}
2258
- onClose={() => this.setState({ viewer: '', formatEditFile: '' })}
2259
- /> : null;
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;
2260
2342
  }
2261
2343
 
2262
- renderError() {
2344
+ renderError(): React.JSX.Element | null {
2263
2345
  if (this.state.errorText) {
2264
- return <ErrorDialog
2265
- key="errorDialog"
2266
- text={this.state.errorText}
2267
- onClose={() => this.setState({ errorText: '' })}
2268
- />;
2346
+ return (
2347
+ <ErrorDialog
2348
+ key="errorDialog"
2349
+ text={this.state.errorText}
2350
+ onClose={() => this.setState({ errorText: '' })}
2351
+ />
2352
+ );
2269
2353
  }
2270
2354
  return null;
2271
2355
  }
2272
2356
 
2273
2357
  // used in tabs/Files
2274
2358
  // eslint-disable-next-line react/no-unused-class-component-methods
2275
- updateItemsAcl(info: FolderOrFileItem[]) {
2359
+ updateItemsAcl(info: FolderOrFileItem[]): void {
2276
2360
  this.cacheFolders = this.cacheFolders || JSON.parse(JSON.stringify(this.state.folders));
2277
2361
  let changed;
2278
2362
 
@@ -2296,7 +2380,7 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2296
2380
  }
2297
2381
  }
2298
2382
 
2299
- changeToPath() {
2383
+ changeToPath(): void {
2300
2384
  setTimeout(() => {
2301
2385
  if (this.state.path !== this.state.selected && (!this.lastSelect || Date.now() - this.lastSelect > 100)) {
2302
2386
  let folder = this.state.path;
@@ -2313,79 +2397,100 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2313
2397
  err === NOT_FOUND
2314
2398
  ? this.props.t('ra_Cannot find "%s"', folder)
2315
2399
  : this.props.t('ra_Cannot read "%s"', folder),
2316
- }));
2400
+ }),
2401
+ );
2317
2402
  } else {
2318
2403
  resolve(true);
2319
2404
  }
2320
2405
  })
2321
- .then(result =>
2322
- result && this.setState({ selected: this.state.path, currentDir: folder, pathFocus: false }));
2406
+ .then(
2407
+ result =>
2408
+ result &&
2409
+ this.setState({ selected: this.state.path, currentDir: folder, pathFocus: false }),
2410
+ )
2411
+ .catch(e => console.error(e));
2323
2412
  } else if (!this.lastSelect || Date.now() - this.lastSelect > 100) {
2324
2413
  this.setState({ pathFocus: false });
2325
2414
  }
2326
2415
  }, 100);
2327
2416
  }
2328
2417
 
2329
- renderBreadcrumb() {
2418
+ renderBreadcrumb(): React.JSX.Element {
2330
2419
  const parts = this.state.currentDir.startsWith('/')
2331
2420
  ? this.state.currentDir.split('/')
2332
2421
  : `/${this.state.currentDir}`.split('/');
2333
2422
  const p: string[] = [];
2334
- return <Breadcrumbs style={{ paddingLeft: 8 }}>
2335
- {parts.map((part, i) => {
2336
- if (part) {
2337
- p.push(part);
2338
- }
2339
- const path = p.join('/');
2340
- if (i < parts.length - 1) {
2341
- return <Box
2342
- component="div"
2343
- key={`${this.state.selected}_${i}`}
2344
- sx={styles.pathDivBreadcrumbDir}
2345
- onClick={e => this.changeFolder(e, path || '/')}
2346
- >
2347
- {part || this.props.t('ra_Root')}
2348
- </Box>;
2349
- }
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
+ }
2350
2442
 
2351
- return <div
2352
- style={styles.pathDivBreadcrumbSelected}
2353
- key={`${this.state.selected}_${i}`}
2354
- onClick={() => this.setState({ pathFocus: true })}
2355
- >
2356
- {part}
2357
- </div>;
2358
- })}
2359
- </Breadcrumbs>;
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
+ );
2360
2455
  }
2361
2456
 
2362
- renderPath() {
2363
- return <Box component="div" key="path" sx={styles.pathDiv}>
2364
- {this.state.pathFocus ?
2365
- <Input
2366
- value={this.state.path}
2367
- onKeyDown={e => {
2368
- if (e.key === 'Enter') {
2369
- this.changeToPath();
2370
- } else if (e.key === 'Escape') {
2371
- this.setState({ pathFocus: false });
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>
2372
2481
  }
2373
- }}
2374
- endAdornment={
2375
- <IconButton size="small" onClick={() => this.changeToPath()}>
2376
- <EnterIcon />
2377
- </IconButton>
2378
- }
2379
- onBlur={() => this.changeToPath()}
2380
- onChange={e => this.setState({ path: e.target.value })}
2381
- style={styles.pathDivInput}
2382
- />
2383
- :
2384
- this.renderBreadcrumb()}
2385
- </Box>;
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
+ );
2386
2491
  }
2387
2492
 
2388
- render() {
2493
+ render(): React.JSX.Element {
2389
2494
  if (!this.props.ready) {
2390
2495
  return <LinearProgress />;
2391
2496
  }
@@ -2395,48 +2500,50 @@ export class FileBrowserClass extends Component<FileBrowserProps, FileBrowserSta
2395
2500
  setTimeout(() => {
2396
2501
  this.setState({ loadAllFolders: false, folders: {} }, () => {
2397
2502
  this.foldersLoading = false;
2398
- this.loadFolders()
2399
- .catch(error => console.error(`Cannot load folders: ${error}`));
2503
+ this.loadFolders().catch(error => console.error(`Cannot load folders: ${error}`));
2400
2504
  });
2401
2505
  }, 300);
2402
2506
  }
2403
2507
 
2404
- return <div
2405
- style={{ ...styles.root, ...this.props.style }}
2406
- className={this.props.className}
2407
- >
2408
- {this.props.showToolbar ? this.renderToolbar() : null}
2409
- {this.state.viewType === TILE ? this.renderPath() : null}
2508
+ return (
2410
2509
  <div
2411
- style={{
2412
- ...styles.filesDiv,
2413
- ...styles[`filesDiv${this.state.viewType}`],
2414
- }}
2415
- onClick={e => {
2416
- if (this.state.viewType !== TABLE) {
2417
- if (this.state.selected !== (this.state.currentDir || '/')) {
2418
- this.changeFolder(e, this.state.currentDir || '/');
2419
- } else {
2420
- e.stopPropagation();
2421
- }
2422
- }
2423
- }}
2510
+ style={{ ...styles.root, ...this.props.style }}
2511
+ className={this.props.className}
2424
2512
  >
2425
- {this.state.viewType === TABLE
2426
- ? this.renderItems('/')
2427
- : this.renderItems(this.state.currentDir || '/')}
2428
- {this.state.viewType !== TABLE ?
2429
- <div style={styles.filesDivHint}>{this.props.t('ra_select_folder_hint')}</div> : null}
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()}
2430
2545
  </div>
2431
- {this.props.allowUpload ? this.renderInputDialog() : null}
2432
- {this.props.allowUpload ? this.renderUpload() : null}
2433
- {this.props.allowDelete ? this.renderDeleteDialog() : null}
2434
- {this.props.allowView ? this.renderViewDialog() : null}
2435
- {this.state.modalEditOfAccess && this.props.modalEditOfAccessControl
2436
- ? this.props.modalEditOfAccessControl(this)
2437
- : null}
2438
- {this.renderError()}
2439
- </div>;
2546
+ );
2440
2547
  }
2441
2548
  }
2442
2549