dbf-mirador 4.2.3

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 (384) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +133 -0
  3. package/dist/CommentSharp-B0J_-jEq.js +325 -0
  4. package/dist/CommentSharp-B0J_-jEq.js.map +1 -0
  5. package/dist/FailedImageContext-aQXnWSHN.js +318 -0
  6. package/dist/FailedImageContext-aQXnWSHN.js.map +1 -0
  7. package/dist/PluginHook-CFw_6P5c.js +54 -0
  8. package/dist/PluginHook-CFw_6P5c.js.map +1 -0
  9. package/dist/WindowCanvasNavigationControls-CUX723BQ.js +242 -0
  10. package/dist/WindowCanvasNavigationControls-CUX723BQ.js.map +1 -0
  11. package/dist/action-types-DjIbTE-A.js +74 -0
  12. package/dist/action-types-DjIbTE-A.js.map +1 -0
  13. package/dist/canvas-C8oHvXFN.js +41 -0
  14. package/dist/canvas-C8oHvXFN.js.map +1 -0
  15. package/dist/canvases-gRqhr-IH.js +3154 -0
  16. package/dist/canvases-gRqhr-IH.js.map +1 -0
  17. package/dist/createSvgIcon-Cwt8YPfm.js +2 -0
  18. package/dist/getters-COus7Mvg.js +2066 -0
  19. package/dist/getters-COus7Mvg.js.map +1 -0
  20. package/dist/index.module-CbBRf_R8.js +403 -0
  21. package/dist/index.module-CbBRf_R8.js.map +1 -0
  22. package/dist/mirador.es.js +39600 -0
  23. package/dist/mirador.es.js.map +1 -0
  24. package/dist/mirador.min.js +334 -0
  25. package/dist/mirador.min.js.map +1 -0
  26. package/dist/react-error-boundary.esm-B_pT-oCS.js +67 -0
  27. package/dist/react-error-boundary.esm-B_pT-oCS.js.map +1 -0
  28. package/dist/visuallyHidden-DmYvwNdU.js +98 -0
  29. package/dist/visuallyHidden-DmYvwNdU.js.map +1 -0
  30. package/dist/window-zelwU7wz.js +138 -0
  31. package/dist/window-zelwU7wz.js.map +1 -0
  32. package/package.json +138 -0
  33. package/src/components/AccessTokenSender.jsx +34 -0
  34. package/src/components/AnnotationSettings.jsx +31 -0
  35. package/src/components/AnnotationsOverlay.jsx +328 -0
  36. package/src/components/App.jsx +34 -0
  37. package/src/components/AppProviders.jsx +129 -0
  38. package/src/components/AttributionPanel.jsx +78 -0
  39. package/src/components/AudioViewer.jsx +39 -0
  40. package/src/components/BackgroundPluginArea.jsx +9 -0
  41. package/src/components/Branding.jsx +33 -0
  42. package/src/components/CanvasAnnotations.jsx +125 -0
  43. package/src/components/CanvasInfo.jsx +46 -0
  44. package/src/components/CanvasLayers.jsx +367 -0
  45. package/src/components/ChangeThemeDialog.jsx +65 -0
  46. package/src/components/CollapsibleSection.jsx +53 -0
  47. package/src/components/CollectionDialog.jsx +236 -0
  48. package/src/components/CollectionInfo.jsx +48 -0
  49. package/src/components/CompanionArea.jsx +140 -0
  50. package/src/components/CompanionWindow.jsx +228 -0
  51. package/src/components/CompanionWindowFactory.jsx +38 -0
  52. package/src/components/CompanionWindowSection.jsx +11 -0
  53. package/src/components/CustomPanel.jsx +22 -0
  54. package/src/components/ErrorContent.jsx +65 -0
  55. package/src/components/ErrorDialog.jsx +47 -0
  56. package/src/components/FullScreenButton.jsx +47 -0
  57. package/src/components/GalleryView.jsx +36 -0
  58. package/src/components/GalleryViewThumbnail.jsx +160 -0
  59. package/src/components/IIIFAuthentication.jsx +144 -0
  60. package/src/components/IIIFDropTarget.jsx +168 -0
  61. package/src/components/IIIFIFrameCommunication.jsx +45 -0
  62. package/src/components/IIIFResourceLabel.jsx +26 -0
  63. package/src/components/IIIFThumbnail.jsx +214 -0
  64. package/src/components/ImageFailureMessage.jsx +42 -0
  65. package/src/components/LabelValueMetadata.jsx +46 -0
  66. package/src/components/LanguageSettings.jsx +46 -0
  67. package/src/components/LayersPanel.jsx +24 -0
  68. package/src/components/LocalePicker.jsx +56 -0
  69. package/src/components/ManifestForm.jsx +79 -0
  70. package/src/components/ManifestInfo.jsx +67 -0
  71. package/src/components/ManifestListItem.jsx +209 -0
  72. package/src/components/ManifestListItemError.jsx +53 -0
  73. package/src/components/ManifestRelatedLinks.jsx +154 -0
  74. package/src/components/MinimalWindow.jsx +104 -0
  75. package/src/components/MiradorMenuButton.jsx +71 -0
  76. package/src/components/MosaicRenderPreview.jsx +16 -0
  77. package/src/components/NestedMenu.jsx +42 -0
  78. package/src/components/NewBrowserWindow.jsx +34 -0
  79. package/src/components/OpenSeadragonComponent.jsx +258 -0
  80. package/src/components/OpenSeadragonTileSource.jsx +105 -0
  81. package/src/components/OpenSeadragonViewer.jsx +161 -0
  82. package/src/components/PluginHook.jsx +27 -0
  83. package/src/components/PrimaryWindow.jsx +115 -0
  84. package/src/components/SanitizedHtml.jsx +37 -0
  85. package/src/components/ScrollIndicatedDialogContent.jsx +60 -0
  86. package/src/components/ScrollTo.jsx +61 -0
  87. package/src/components/SearchHit.jsx +220 -0
  88. package/src/components/SearchPanel.jsx +80 -0
  89. package/src/components/SearchPanelControls.jsx +174 -0
  90. package/src/components/SearchPanelNavigation.jsx +84 -0
  91. package/src/components/SearchResults.jsx +138 -0
  92. package/src/components/SelectCollection.jsx +44 -0
  93. package/src/components/SidebarIndexItem.jsx +11 -0
  94. package/src/components/SidebarIndexList.jsx +73 -0
  95. package/src/components/SidebarIndexTableOfContents.jsx +170 -0
  96. package/src/components/SidebarIndexThumbnail.jsx +22 -0
  97. package/src/components/TextViewer.jsx +37 -0
  98. package/src/components/ThumbnailCanvasGrouping.jsx +122 -0
  99. package/src/components/ThumbnailNavigation.jsx +231 -0
  100. package/src/components/VideoViewer.jsx +35 -0
  101. package/src/components/ViewerInfo.jsx +55 -0
  102. package/src/components/ViewerNavigation.jsx +72 -0
  103. package/src/components/Window.jsx +150 -0
  104. package/src/components/WindowAuthenticationBar.jsx +176 -0
  105. package/src/components/WindowCanvasNavigationControls.jsx +91 -0
  106. package/src/components/WindowList.jsx +71 -0
  107. package/src/components/WindowListButton.jsx +57 -0
  108. package/src/components/WindowSideBar.jsx +48 -0
  109. package/src/components/WindowSideBarAnnotationsPanel.jsx +51 -0
  110. package/src/components/WindowSideBarButtons.jsx +151 -0
  111. package/src/components/WindowSideBarCanvasPanel.jsx +165 -0
  112. package/src/components/WindowSideBarCollectionPanel.jsx +179 -0
  113. package/src/components/WindowSideBarInfoPanel.jsx +67 -0
  114. package/src/components/WindowThumbnailSettings.jsx +130 -0
  115. package/src/components/WindowTopBar.jsx +125 -0
  116. package/src/components/WindowTopBarPluginArea.jsx +8 -0
  117. package/src/components/WindowTopBarPluginMenu.jsx +78 -0
  118. package/src/components/WindowTopBarTitle.jsx +73 -0
  119. package/src/components/WindowTopMenu.jsx +81 -0
  120. package/src/components/WindowTopMenuButton.jsx +49 -0
  121. package/src/components/WindowViewSettings.jsx +95 -0
  122. package/src/components/WindowViewer.jsx +26 -0
  123. package/src/components/Workspace.jsx +124 -0
  124. package/src/components/WorkspaceAdd.jsx +221 -0
  125. package/src/components/WorkspaceAddButton.jsx +73 -0
  126. package/src/components/WorkspaceArea.jsx +66 -0
  127. package/src/components/WorkspaceControlPanel.jsx +85 -0
  128. package/src/components/WorkspaceControlPanelButtons.jsx +20 -0
  129. package/src/components/WorkspaceDialog.jsx +11 -0
  130. package/src/components/WorkspaceElastic.jsx +88 -0
  131. package/src/components/WorkspaceElasticWindow.jsx +79 -0
  132. package/src/components/WorkspaceExport.jsx +99 -0
  133. package/src/components/WorkspaceImport.jsx +89 -0
  134. package/src/components/WorkspaceMenu.jsx +126 -0
  135. package/src/components/WorkspaceMenuButton.jsx +42 -0
  136. package/src/components/WorkspaceMosaic.jsx +149 -0
  137. package/src/components/WorkspaceOptionsButton.jsx +35 -0
  138. package/src/components/WorkspaceOptionsMenu.jsx +112 -0
  139. package/src/components/WorkspaceSelectionDialog.jsx +165 -0
  140. package/src/components/ZoomControls.jsx +57 -0
  141. package/src/components/icons/BookViewIcon.jsx +14 -0
  142. package/src/components/icons/CanvasIndexIcon.jsx +14 -0
  143. package/src/components/icons/GalleryViewIcon.jsx +17 -0
  144. package/src/components/icons/MiradorIcon.jsx +14 -0
  145. package/src/components/icons/RestoreZoomIcon.jsx +15 -0
  146. package/src/components/icons/ThumbnailNavigationBottomIcon.jsx +15 -0
  147. package/src/components/icons/ThumbnailNavigationRightIcon.jsx +16 -0
  148. package/src/components/icons/WindowMaxIcon.jsx +15 -0
  149. package/src/components/icons/WindowMinIcon.jsx +15 -0
  150. package/src/components/icons/WindowOptionsIcon.jsx +16 -0
  151. package/src/components/icons/WorkspaceTypeElasticIcon.jsx +157 -0
  152. package/src/components/icons/WorkspaceTypeMosaicIcon.jsx +162 -0
  153. package/src/components/icons/index.js +27 -0
  154. package/src/components/index.js +108 -0
  155. package/src/config/css-ns.js +10 -0
  156. package/src/config/index.js +2 -0
  157. package/src/config/settings.js +607 -0
  158. package/src/containers/AnnotationSettings.js +27 -0
  159. package/src/containers/AnnotationsOverlay.js +51 -0
  160. package/src/containers/AppProviders.js +20 -0
  161. package/src/containers/AttributionPanel.js +20 -0
  162. package/src/containers/AudioViewer.js +16 -0
  163. package/src/containers/BackgroundPluginArea.js +4 -0
  164. package/src/containers/Branding.js +4 -0
  165. package/src/containers/CanvasAnnotations.js +32 -0
  166. package/src/containers/CanvasInfo.js +20 -0
  167. package/src/containers/CanvasLayers.js +25 -0
  168. package/src/containers/ChangeThemeDialog.js +29 -0
  169. package/src/containers/CollapsibleSection.js +3 -0
  170. package/src/containers/CollectionDialog.js +48 -0
  171. package/src/containers/CollectionInfo.js +29 -0
  172. package/src/containers/CompanionArea.js +22 -0
  173. package/src/containers/CompanionWindow.js +40 -0
  174. package/src/containers/CompanionWindowFactory.js +23 -0
  175. package/src/containers/CustomPanel.js +13 -0
  176. package/src/containers/ErrorContent.js +20 -0
  177. package/src/containers/ErrorDialog.js +28 -0
  178. package/src/containers/FullScreenButton.js +22 -0
  179. package/src/containers/GalleryView.js +23 -0
  180. package/src/containers/GalleryViewThumbnail.js +51 -0
  181. package/src/containers/IIIFAuthentication.js +86 -0
  182. package/src/containers/IIIFThumbnail.js +17 -0
  183. package/src/containers/LabelValueMetadata.js +14 -0
  184. package/src/containers/LanguageSettings.js +26 -0
  185. package/src/containers/LayersPanel.js +16 -0
  186. package/src/containers/LocalePicker.js +3 -0
  187. package/src/containers/ManifestForm.js +16 -0
  188. package/src/containers/ManifestInfo.js +29 -0
  189. package/src/containers/ManifestListItem.js +52 -0
  190. package/src/containers/ManifestListItemError.js +15 -0
  191. package/src/containers/ManifestRelatedLinks.js +28 -0
  192. package/src/containers/MinimalWindow.js +25 -0
  193. package/src/containers/MiradorMenuButton.js +7 -0
  194. package/src/containers/MosaicRenderPreview.js +14 -0
  195. package/src/containers/OpenSeadragonViewer.js +59 -0
  196. package/src/containers/PrimaryWindow.js +27 -0
  197. package/src/containers/SanitizedHtml.js +3 -0
  198. package/src/containers/ScrollIndicatedDialogContent.js +3 -0
  199. package/src/containers/SearchHit.js +71 -0
  200. package/src/containers/SearchPanel.js +23 -0
  201. package/src/containers/SearchPanelControls.js +36 -0
  202. package/src/containers/SearchPanelNavigation.js +39 -0
  203. package/src/containers/SearchResults.js +38 -0
  204. package/src/containers/SelectCollection.js +24 -0
  205. package/src/containers/SidebarIndexItem.js +8 -0
  206. package/src/containers/SidebarIndexList.js +28 -0
  207. package/src/containers/SidebarIndexTableOfContents.js +31 -0
  208. package/src/containers/SidebarIndexThumbnail.js +18 -0
  209. package/src/containers/TextViewer.js +19 -0
  210. package/src/containers/ThumbnailCanvasGrouping.js +29 -0
  211. package/src/containers/ThumbnailNavigation.js +48 -0
  212. package/src/containers/VideoViewer.js +16 -0
  213. package/src/containers/ViewerInfo.js +30 -0
  214. package/src/containers/ViewerNavigation.js +31 -0
  215. package/src/containers/Window.js +45 -0
  216. package/src/containers/WindowAuthenticationBar.js +7 -0
  217. package/src/containers/WindowCanvasNavigationControls.js +15 -0
  218. package/src/containers/WindowList.js +30 -0
  219. package/src/containers/WindowListButton.js +15 -0
  220. package/src/containers/WindowSideBar.js +20 -0
  221. package/src/containers/WindowSideBarAnnotationsPanel.js +23 -0
  222. package/src/containers/WindowSideBarButtons.js +75 -0
  223. package/src/containers/WindowSideBarCanvasPanel.js +50 -0
  224. package/src/containers/WindowSideBarCollectionPanel.js +48 -0
  225. package/src/containers/WindowSideBarInfoPanel.js +35 -0
  226. package/src/containers/WindowThumbnailSettings.js +31 -0
  227. package/src/containers/WindowTopBar.js +39 -0
  228. package/src/containers/WindowTopBarPluginArea.js +8 -0
  229. package/src/containers/WindowTopBarPluginMenu.js +7 -0
  230. package/src/containers/WindowTopBarTitle.js +17 -0
  231. package/src/containers/WindowTopMenu.js +28 -0
  232. package/src/containers/WindowTopMenuButton.js +7 -0
  233. package/src/containers/WindowViewSettings.js +30 -0
  234. package/src/containers/WindowViewer.js +10 -0
  235. package/src/containers/Workspace.js +36 -0
  236. package/src/containers/WorkspaceAdd.js +27 -0
  237. package/src/containers/WorkspaceAddButton.js +30 -0
  238. package/src/containers/WorkspaceArea.js +21 -0
  239. package/src/containers/WorkspaceControlPanel.js +10 -0
  240. package/src/containers/WorkspaceControlPanelButtons.js +7 -0
  241. package/src/containers/WorkspaceElastic.js +41 -0
  242. package/src/containers/WorkspaceElasticWindow.js +36 -0
  243. package/src/containers/WorkspaceExport.js +18 -0
  244. package/src/containers/WorkspaceImport.js +19 -0
  245. package/src/containers/WorkspaceMenu.js +30 -0
  246. package/src/containers/WorkspaceMenuButton.js +10 -0
  247. package/src/containers/WorkspaceMosaic.js +32 -0
  248. package/src/containers/WorkspaceOptionsButton.js +7 -0
  249. package/src/containers/WorkspaceOptionsMenu.js +3 -0
  250. package/src/containers/WorkspaceSelectionDialog.js +26 -0
  251. package/src/containers/ZoomControls.js +26 -0
  252. package/src/containers/index.js +94 -0
  253. package/src/contexts/FailedImageContext.js +8 -0
  254. package/src/contexts/FailedImageProvider.jsx +34 -0
  255. package/src/contexts/FullScreenContext.js +5 -0
  256. package/src/contexts/LocaleContext.js +5 -0
  257. package/src/contexts/OpenSeadragonViewerContext.js +5 -0
  258. package/src/contexts/WorkspaceContext.js +5 -0
  259. package/src/contexts/index.js +4 -0
  260. package/src/extend/PluginContext.js +5 -0
  261. package/src/extend/PluginProvider.jsx +22 -0
  262. package/src/extend/index.js +9 -0
  263. package/src/extend/pluginMapping.js +44 -0
  264. package/src/extend/pluginPreprocessing.js +45 -0
  265. package/src/extend/pluginValidation.js +58 -0
  266. package/src/extend/usePlugins.js +13 -0
  267. package/src/extend/withPlugins.jsx +46 -0
  268. package/src/hooks/index.js +4 -0
  269. package/src/hooks/useCanvasWorldService.js +10 -0
  270. package/src/hooks/useThumbnailService.js +8 -0
  271. package/src/i18n.js +71 -0
  272. package/src/index.js +18 -0
  273. package/src/init.js +20 -0
  274. package/src/lib/AnnotationFactory.js +23 -0
  275. package/src/lib/AnnotationItem.js +123 -0
  276. package/src/lib/AnnotationList.js +31 -0
  277. package/src/lib/AnnotationPage.js +41 -0
  278. package/src/lib/AnnotationResource.js +122 -0
  279. package/src/lib/CanvasAnnotationDisplay.js +148 -0
  280. package/src/lib/CanvasGroupings.js +55 -0
  281. package/src/lib/CanvasWorld.js +214 -0
  282. package/src/lib/CompanionWindowRegistry.js +23 -0
  283. package/src/lib/FullScreenHandle.jsx +76 -0
  284. package/src/lib/MiradorCanvas.js +211 -0
  285. package/src/lib/MiradorManifest.js +44 -0
  286. package/src/lib/MiradorViewer.jsx +53 -0
  287. package/src/lib/MosaicLayout.js +77 -0
  288. package/src/lib/OpenSeadragonCanvasOverlay.js +114 -0
  289. package/src/lib/ThumbnailFactory.js +310 -0
  290. package/src/lib/TruncatedHit.js +34 -0
  291. package/src/lib/asArray.js +11 -0
  292. package/src/lib/htmlRules.js +28 -0
  293. package/src/lib/iiif.js +12 -0
  294. package/src/lib/index.js +23 -0
  295. package/src/lib/readImageMetadata.js +21 -0
  296. package/src/lib/svgShapesToPath.js +49 -0
  297. package/src/locales/README.md +60 -0
  298. package/src/locales/ar/translation.json +141 -0
  299. package/src/locales/bg/translation.json +164 -0
  300. package/src/locales/de/translation.json +169 -0
  301. package/src/locales/en/translation.json +170 -0
  302. package/src/locales/et/translation.json +164 -0
  303. package/src/locales/fa/translation.json +164 -0
  304. package/src/locales/fr/translation.json +158 -0
  305. package/src/locales/hr/translation.json +164 -0
  306. package/src/locales/hu/translation.json +166 -0
  307. package/src/locales/it/translation.json +162 -0
  308. package/src/locales/ja/translation.json +156 -0
  309. package/src/locales/kr/translation.json +162 -0
  310. package/src/locales/lt/translation.json +153 -0
  311. package/src/locales/nbNo/translation.json +162 -0
  312. package/src/locales/nl/translation.json +139 -0
  313. package/src/locales/pl/translation.json +162 -0
  314. package/src/locales/ptBr/translation.json +133 -0
  315. package/src/locales/sr/translation.json +155 -0
  316. package/src/locales/sv/translation.json +157 -0
  317. package/src/locales/ta/translation.json +167 -0
  318. package/src/locales/vi/translation.json +156 -0
  319. package/src/locales/zhCn/translation.json +163 -0
  320. package/src/locales/zhTw/translation.json +163 -0
  321. package/src/plugins/OSDReferences.js +15 -0
  322. package/src/plugins/index.js +1 -0
  323. package/src/state/actions/action-types.js +76 -0
  324. package/src/state/actions/annotation.js +123 -0
  325. package/src/state/actions/auth.js +109 -0
  326. package/src/state/actions/canvas.js +65 -0
  327. package/src/state/actions/catalog.js +22 -0
  328. package/src/state/actions/companionWindow.js +107 -0
  329. package/src/state/actions/config.js +49 -0
  330. package/src/state/actions/elasticLayout.js +10 -0
  331. package/src/state/actions/errors.js +22 -0
  332. package/src/state/actions/index.js +19 -0
  333. package/src/state/actions/infoResponse.js +90 -0
  334. package/src/state/actions/layers.js +14 -0
  335. package/src/state/actions/manifest.js +65 -0
  336. package/src/state/actions/search.js +97 -0
  337. package/src/state/actions/window.js +209 -0
  338. package/src/state/actions/workspace.js +84 -0
  339. package/src/state/createPluggableStore.js +24 -0
  340. package/src/state/createStore.js +33 -0
  341. package/src/state/index.js +21 -0
  342. package/src/state/reducers/accessTokens.js +59 -0
  343. package/src/state/reducers/annotations.js +48 -0
  344. package/src/state/reducers/auth.js +43 -0
  345. package/src/state/reducers/catalog.js +30 -0
  346. package/src/state/reducers/companionWindows.js +49 -0
  347. package/src/state/reducers/config.js +39 -0
  348. package/src/state/reducers/elasticLayout.js +29 -0
  349. package/src/state/reducers/errors.js +51 -0
  350. package/src/state/reducers/index.js +15 -0
  351. package/src/state/reducers/infoResponses.js +61 -0
  352. package/src/state/reducers/layers.js +23 -0
  353. package/src/state/reducers/manifests.js +52 -0
  354. package/src/state/reducers/rootReducer.js +44 -0
  355. package/src/state/reducers/search.js +145 -0
  356. package/src/state/reducers/utils.js +45 -0
  357. package/src/state/reducers/viewers.js +42 -0
  358. package/src/state/reducers/windows.js +185 -0
  359. package/src/state/reducers/workspace.js +111 -0
  360. package/src/state/sagas/annotations.js +48 -0
  361. package/src/state/sagas/app.js +56 -0
  362. package/src/state/sagas/auth.js +151 -0
  363. package/src/state/sagas/iiif.js +233 -0
  364. package/src/state/sagas/index.js +30 -0
  365. package/src/state/sagas/windows.js +293 -0
  366. package/src/state/selectors/annotations.js +138 -0
  367. package/src/state/selectors/auth.js +100 -0
  368. package/src/state/selectors/canvases.js +260 -0
  369. package/src/state/selectors/companionWindows.js +182 -0
  370. package/src/state/selectors/config.js +100 -0
  371. package/src/state/selectors/getters.js +90 -0
  372. package/src/state/selectors/index.js +17 -0
  373. package/src/state/selectors/layers.js +79 -0
  374. package/src/state/selectors/manifests.js +433 -0
  375. package/src/state/selectors/ranges.js +147 -0
  376. package/src/state/selectors/searches.js +249 -0
  377. package/src/state/selectors/sequences.js +134 -0
  378. package/src/state/selectors/thumbnails.js +28 -0
  379. package/src/state/selectors/utils.js +18 -0
  380. package/src/state/selectors/viewer.js +19 -0
  381. package/src/state/selectors/windows.js +98 -0
  382. package/src/state/selectors/workspace.js +52 -0
  383. package/src/state/selectors/wrappers.js +15 -0
  384. package/src/styles/react-mosaic-component.js +288 -0
@@ -0,0 +1,168 @@
1
+ import PropTypes from 'prop-types';
2
+ import Backdrop from '@mui/material/Backdrop';
3
+ import InsertDriveFileSharpIcon from '@mui/icons-material/InsertDriveFileSharp';
4
+ import { grey } from '@mui/material/colors';
5
+ import { v4 as uuid } from 'uuid';
6
+ import { NativeTypes } from 'react-dnd-html5-backend';
7
+ import { useDrop } from 'react-dnd';
8
+ import { readImageMetadata } from '../lib/readImageMetadata';
9
+
10
+ /** */
11
+ const safeParseURL = (str) => {
12
+ try {
13
+ return new URL(str);
14
+ } catch (e) {
15
+ console.warn('Invalid URL:', str);
16
+ return null;
17
+ }
18
+ };
19
+
20
+ /** */
21
+ export const handleDrop = (item, monitor, props) => {
22
+ const { onDrop } = props;
23
+
24
+ if (item.html) {
25
+ const doc = new DOMParser().parseFromString(item.html, 'text/html');
26
+
27
+ // Try to find a link tag
28
+ const link = doc.querySelector('a[href]');
29
+ if (link) {
30
+ const url = safeParseURL(link.href);
31
+ if (url) {
32
+ // Recursively call the method again with the extracted URL
33
+ return handleDrop({ urls: [url.toString()] }, monitor, props);
34
+ }
35
+ }
36
+ }
37
+
38
+ if (item.urls) {
39
+ item.urls.forEach((str) => {
40
+ const url = safeParseURL(str);
41
+ if (!url) return;
42
+
43
+ const manifestId = url.searchParams.get('manifest');
44
+ const canvasId = url.searchParams.get('canvas');
45
+
46
+ if (manifestId) onDrop({ canvasId, manifestId }, props, monitor);
47
+ });
48
+ }
49
+
50
+ if (item.files) {
51
+ const manifestFiles = item.files.filter((f) => f.type === 'application/json');
52
+ const manifestPromises = manifestFiles.map(
53
+ (file) =>
54
+ new Promise((resolve, reject) => {
55
+ const reader = new FileReader();
56
+ reader.addEventListener('load', () => {
57
+ const manifestJson = reader.result;
58
+ const manifestId = uuid();
59
+
60
+ if (manifestJson) onDrop({ manifestId, manifestJson }, props, monitor);
61
+ resolve();
62
+ });
63
+ reader.readAsText(file);
64
+ }),
65
+ );
66
+
67
+ const imageFiles = item.files.filter(({ type }) => type.startsWith('image/'));
68
+
69
+ let imagePromise;
70
+
71
+ if (imageFiles.length > 0) {
72
+ const id = uuid();
73
+ const imageData = imageFiles.map((file) => readImageMetadata(file));
74
+
75
+ imagePromise = Promise.all(imageData).then((images) => {
76
+ const manifestJson = {
77
+ '@context': 'http://iiif.io/api/presentation/3/context.json',
78
+ id,
79
+ items: images.map(({ name, type, width, height, url }, index) => ({
80
+ height,
81
+ id: `${id}/canvas/${index}`,
82
+ items: [
83
+ {
84
+ id: `${id}/canvas/${index}/1`,
85
+ items: [
86
+ {
87
+ body: {
88
+ format: type,
89
+ id: url,
90
+ type: 'Image',
91
+ },
92
+ height,
93
+ id: `${id}/canvas/${index}/1/image`,
94
+ motivation: 'painting',
95
+ target: `${id}/canvas/${index}/1`,
96
+ type: 'Annotation',
97
+ width,
98
+ },
99
+ ],
100
+ type: 'AnnotationPage',
101
+ },
102
+ ],
103
+ label: name,
104
+ type: 'Canvas',
105
+ width,
106
+ })),
107
+ label: images[0].name,
108
+ type: 'Manifest',
109
+ };
110
+
111
+ const manifestId = uuid();
112
+ if (manifestJson) onDrop({ manifestId, manifestJson }, props, monitor);
113
+ });
114
+ }
115
+
116
+ return Promise.all([...manifestPromises, imagePromise]);
117
+ }
118
+
119
+ return undefined;
120
+ };
121
+
122
+ /** */
123
+ export const IIIFDropTarget = (props) => {
124
+ const { children, onDrop } = props;
125
+ const [{ canDrop, isOver }, drop] = useDrop({
126
+ accept: [NativeTypes.URL, NativeTypes.FILE, NativeTypes.HTML],
127
+ collect: (monitor) => ({
128
+ canDrop: monitor.canDrop(),
129
+ isOver: monitor.isOver(),
130
+ }),
131
+ /** */
132
+ drop(item, monitor) {
133
+ if (!onDrop) return;
134
+ handleDrop(item, monitor, props);
135
+ },
136
+ });
137
+
138
+ /**
139
+ * Safari reports drag+drop'ed urls as both a file and uri-list
140
+ * which gets mis-classified by react-dnd.
141
+ */
142
+ const hackForSafari = (e) => {
143
+ if (!window.safari || !onDrop || !e.dataTransfer) return;
144
+
145
+ if (e.dataTransfer.types.includes('Files') && e.dataTransfer.types.includes('text/uri-list')) {
146
+ const url = e.dataTransfer.getData('text/uri-list');
147
+
148
+ if (!url) return;
149
+ handleDrop({ urls: [url] }, null, props);
150
+ }
151
+ };
152
+
153
+ const isActive = canDrop && isOver;
154
+
155
+ return (
156
+ <div ref={drop} onDrop={hackForSafari} style={{ height: '100%', width: '100%' }}>
157
+ {children}
158
+ <Backdrop open={isActive} style={{ zIndex: 9999 }}>
159
+ <InsertDriveFileSharpIcon style={{ color: grey[400], fontSize: 256 }} />
160
+ </Backdrop>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ IIIFDropTarget.propTypes = {
166
+ children: PropTypes.node.isRequired,
167
+ onDrop: PropTypes.func.isRequired,
168
+ };
@@ -0,0 +1,45 @@
1
+ import { useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const IIIFIFrameCommunicationDefaultProps = {
5
+ 'aria-hidden': true,
6
+ frameBorder: 0,
7
+ height: 1,
8
+ name: undefined,
9
+ scrolling: undefined,
10
+ style: { visibility: 'hidden' },
11
+ width: 1,
12
+ };
13
+
14
+ /**
15
+ * Handle IIIF Auth token validation using iframe message events
16
+ */
17
+ export function IIIFIFrameCommunication({ handleReceiveMessage = undefined, ...props }) {
18
+ // Attaches the 'message' event listener to the window.
19
+ useEffect(() => {
20
+ if (!handleReceiveMessage) return undefined;
21
+
22
+ window.addEventListener('message', handleReceiveMessage);
23
+
24
+ // cleanup function
25
+ return () => window.removeEventListener('message', handleReceiveMessage, false);
26
+ }, [handleReceiveMessage]);
27
+
28
+ return (
29
+ // iframe "title" attribute is passed in via props for accessibility
30
+ /* eslint-disable jsx-a11y/iframe-has-title */
31
+ <iframe {...IIIFIFrameCommunicationDefaultProps} {...props} />
32
+ );
33
+ }
34
+
35
+ IIIFIFrameCommunication.propTypes = {
36
+ 'aria-hidden': PropTypes.bool,
37
+ frameBorder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
38
+ handleReceiveMessage: PropTypes.func,
39
+ height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
40
+ name: PropTypes.string,
41
+ scrolling: PropTypes.string,
42
+ src: PropTypes.string.isRequired,
43
+ style: PropTypes.shape({}),
44
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
45
+ };
@@ -0,0 +1,26 @@
1
+ import { useContext } from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+ import { getLocale } from '../state/selectors';
5
+ import LocaleContext from '../contexts/LocaleContext';
6
+
7
+ /**
8
+ * Render the contextually appropriate label for the resource
9
+ */
10
+ export function IIIFResourceLabel({ fallback, resource }) {
11
+ const contextLocale = useContext(LocaleContext);
12
+ const fallbackLocale = useSelector((state) => getLocale(state, {}));
13
+
14
+ if (!resource) return fallback;
15
+
16
+ const label = resource.getLabel();
17
+
18
+ if (!label) return fallback;
19
+
20
+ return label.getValue(contextLocale || fallbackLocale || '') ?? (fallback || resource.id);
21
+ }
22
+
23
+ IIIFResourceLabel.propTypes = {
24
+ fallback: PropTypes.string,
25
+ resource: PropTypes.object.isRequired,
26
+ };
@@ -0,0 +1,214 @@
1
+ import { useMemo, useEffect, useState, useContext } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { styled } from '@mui/material/styles';
4
+ import { useInView } from 'react-intersection-observer';
5
+ import { IIIFResourceLabel } from './IIIFResourceLabel';
6
+ import { useThumbnailService } from '../hooks';
7
+ import FailedImageContext from '../contexts/FailedImageContext';
8
+
9
+ const Root = styled('div', { name: 'IIIFThumbnail', slot: 'root' })({});
10
+
11
+ const Label = styled('span', { name: 'IIIFThumbnail', slot: 'label' })(({ theme }) => ({
12
+ ...theme.typography.caption,
13
+ }));
14
+
15
+ const Image = styled('img', { name: 'IIIFThumbnail', slot: 'image' })(() => ({
16
+ height: 'auto',
17
+ width: 'auto',
18
+ }));
19
+
20
+ /**
21
+ * A lazy-loaded image that uses IntersectionObserver to determine when to
22
+ * try to load the image (or even calculate that the image url/height/width are)
23
+ */
24
+ const LazyLoadedImage = ({
25
+ border = false,
26
+ placeholder,
27
+ style = {},
28
+ thumbnail = null,
29
+ resource,
30
+ maxHeight = null,
31
+ maxWidth = null,
32
+ ...props
33
+ }) => {
34
+ const { ref, inView } = useInView();
35
+ const [loaded, setLoaded] = useState(false);
36
+ const [failed, setFailed] = useState(false);
37
+ const thumbnailService = useThumbnailService(maxHeight, maxWidth);
38
+ const { notifyFailure, fallbackImage } = useContext(FailedImageContext);
39
+
40
+ /**
41
+ * Handles the intersection (visibility) of a given thumbnail, by requesting
42
+ * the image and then updating the state.
43
+ */
44
+ useEffect(() => {
45
+ if (loaded || !inView) return;
46
+
47
+ setLoaded(true);
48
+ }, [inView, loaded]);
49
+
50
+ const image = useMemo(() => {
51
+ if (thumbnail) return thumbnail;
52
+
53
+ const i = thumbnailService.get(resource);
54
+
55
+ if (i && i.url) return i;
56
+
57
+ return undefined;
58
+ }, [resource, thumbnail, thumbnailService]);
59
+
60
+ const imageStyles = useMemo(() => {
61
+ const styleProps = {
62
+ height: undefined,
63
+ maxHeight: undefined,
64
+ maxWidth: undefined,
65
+ width: undefined,
66
+ };
67
+
68
+ // If we're using a fallback image due to failure, use object-fit to preserve aspect ratio
69
+ if (failed && fallbackImage) {
70
+ return {
71
+ ...styleProps,
72
+ ...style,
73
+ maxWidth,
74
+ maxHeight,
75
+ objectFit: 'contain',
76
+ };
77
+ }
78
+
79
+ if (!image) return { ...style, height: maxHeight, width: maxWidth };
80
+
81
+ const { height: thumbHeight, width: thumbWidth } = image;
82
+ if (thumbHeight && thumbWidth) {
83
+ if ((maxHeight && thumbHeight > maxHeight) || (maxWidth && thumbWidth > maxWidth)) {
84
+ const aspectRatio = thumbWidth / thumbHeight;
85
+
86
+ if (maxHeight && maxWidth) {
87
+ if (maxWidth / maxHeight < aspectRatio) {
88
+ styleProps.height = Math.round(maxWidth / aspectRatio);
89
+ styleProps.width = maxWidth;
90
+ } else {
91
+ styleProps.height = maxHeight;
92
+ styleProps.width = Math.round(maxHeight * aspectRatio);
93
+ }
94
+ } else if (maxHeight) {
95
+ styleProps.height = maxHeight;
96
+ styleProps.maxWidth = Math.round(maxHeight * aspectRatio);
97
+ } else if (maxWidth) {
98
+ styleProps.width = maxWidth;
99
+ styleProps.maxHeight = Math.round(maxWidth / aspectRatio);
100
+ }
101
+ } else {
102
+ styleProps.width = thumbWidth;
103
+ styleProps.height = thumbHeight;
104
+ }
105
+ } else if (thumbHeight && !thumbWidth) {
106
+ styleProps.height = maxHeight;
107
+ } else if (!thumbHeight && thumbWidth) {
108
+ styleProps.width = maxWidth;
109
+ } else {
110
+ // The thumbnail wasn't retrieved via an Image API service,
111
+ // and its dimensions are not specified in the JSON-LD
112
+ // (note that this may result in a blurry image)
113
+ styleProps.width = maxWidth;
114
+ styleProps.height = maxHeight;
115
+ }
116
+
117
+ return {
118
+ ...styleProps,
119
+ ...style,
120
+ };
121
+ }, [image, maxWidth, maxHeight, style, failed, fallbackImage]);
122
+
123
+ const { url: src = placeholder } = (loaded && (thumbnail || image)) || {};
124
+ // Decide final image source: normal, failed fallback, or placeholder
125
+ const finalSrc = failed ? fallbackImage || placeholder : src;
126
+
127
+ return (
128
+ <Image
129
+ ownerState={{ border }}
130
+ ref={ref}
131
+ alt={failed ? 'Thumbnail image unavailable' : ''}
132
+ role={failed ? undefined : 'presentation'}
133
+ src={finalSrc}
134
+ style={imageStyles}
135
+ onError={() => {
136
+ setFailed(true);
137
+ if (finalSrc) notifyFailure(finalSrc);
138
+ }}
139
+ {...props}
140
+ />
141
+ );
142
+ };
143
+
144
+ LazyLoadedImage.propTypes = {
145
+ border: PropTypes.bool,
146
+ maxHeight: PropTypes.number,
147
+ maxWidth: PropTypes.number,
148
+ placeholder: PropTypes.string.isRequired,
149
+ resource: PropTypes.object.isRequired,
150
+ style: PropTypes.object,
151
+ thumbnail: PropTypes.shape({
152
+ height: PropTypes.number,
153
+ url: PropTypes.string.isRequired,
154
+ width: PropTypes.number,
155
+ }),
156
+ };
157
+
158
+ const defaultPlaceholder =
159
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mMMDQmtBwADgwF/Op8FmAAAAABJRU5ErkJggg==';
160
+
161
+ /**
162
+ * Uses InteractionObserver to "lazy" load canvas thumbnails that are in view.
163
+ */
164
+ export function IIIFThumbnail({
165
+ border = false,
166
+ children = null,
167
+ imagePlaceholder = defaultPlaceholder,
168
+ label = undefined,
169
+ labelled = false,
170
+ maxHeight = null,
171
+ maxWidth = null,
172
+ resource,
173
+ style = {},
174
+ thumbnail = null,
175
+ }) {
176
+ // eslint-disable-next-line prefer-rest-params
177
+ const ownerState = arguments[0];
178
+
179
+ return (
180
+ <Root ownerState={ownerState}>
181
+ <LazyLoadedImage
182
+ placeholder={imagePlaceholder}
183
+ thumbnail={thumbnail}
184
+ resource={resource}
185
+ maxHeight={maxHeight}
186
+ maxWidth={maxWidth}
187
+ style={style}
188
+ border={border}
189
+ />
190
+
191
+ {labelled && <Label ownerState={ownerState}>{label || <IIIFResourceLabel resource={resource} />}</Label>}
192
+ {children}
193
+ </Root>
194
+ );
195
+ }
196
+
197
+ IIIFThumbnail.propTypes = {
198
+ border: PropTypes.bool,
199
+ children: PropTypes.node,
200
+ imagePlaceholder: PropTypes.string,
201
+ label: PropTypes.string,
202
+ labelled: PropTypes.bool,
203
+ maxHeight: PropTypes.number,
204
+ maxWidth: PropTypes.number,
205
+ resource: PropTypes.object.isRequired,
206
+ style: PropTypes.object,
207
+ thumbnail: PropTypes.shape({
208
+ height: PropTypes.number,
209
+ url: PropTypes.string.isRequired,
210
+ width: PropTypes.number,
211
+ }),
212
+ // eslint-disable-next-line react/no-unused-prop-types
213
+ variant: PropTypes.oneOf(['inside', 'outside']),
214
+ };
@@ -0,0 +1,42 @@
1
+ import { useContext } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { styled } from '@mui/material/styles';
4
+ import Typography from '@mui/material/Typography';
5
+ import Box from '@mui/material/Box';
6
+ import PropTypes from 'prop-types';
7
+ import FailedImageContext from '../contexts/FailedImageContext';
8
+
9
+ const MessageContainer = styled(Box)(({ theme }) => ({
10
+ position: 'absolute',
11
+ top: theme.spacing(2),
12
+ left: '50%',
13
+ transform: 'translateX(-50%)',
14
+ backgroundColor: theme.palette.primary.main,
15
+ color: theme.palette.common.white,
16
+ padding: theme.spacing(1, 2),
17
+ borderRadius: theme.shape.borderRadius,
18
+ zIndex: 1000,
19
+ pointerEvents: 'none',
20
+ }));
21
+
22
+ /**
23
+ * Displays an accessible message when images fail to load in the OSD viewer
24
+ */
25
+ export function ImageFailureMessage({ imageUrls = [] }) {
26
+ const { failedImages } = useContext(FailedImageContext);
27
+ const { t } = useTranslation();
28
+
29
+ const hasFailedImage = imageUrls.some((url) => failedImages.has(url));
30
+
31
+ if (!hasFailedImage) return null;
32
+
33
+ return (
34
+ <MessageContainer role="status" aria-live="polite">
35
+ <Typography variant="body2">{t('imageFailedToLoad')}</Typography>
36
+ </MessageContainer>
37
+ );
38
+ }
39
+
40
+ ImageFailureMessage.propTypes = {
41
+ imageUrls: PropTypes.arrayOf(PropTypes.string),
42
+ };
@@ -0,0 +1,46 @@
1
+ import PropTypes from 'prop-types';
2
+ import Typography from '@mui/material/Typography';
3
+ import SanitizedHtml from '../containers/SanitizedHtml';
4
+ import ns from '../config/css-ns';
5
+
6
+ /**
7
+ * Renders label/value pair metadata in a dl
8
+ * @prop {object} labelValuePair
9
+ */
10
+ export function LabelValueMetadata({ defaultLabel = undefined, labelValuePairs, labelValueJoiner = ', ' }) {
11
+ if (labelValuePairs.length === 0) {
12
+ return null;
13
+ }
14
+
15
+ /* eslint-disable react/no-array-index-key */
16
+ // Disabling array index key for dt/dd elements as
17
+ // they are intended to display metadata that will not
18
+ // need to be re-rendered internally in any meaningful way
19
+ return (
20
+ <dl className={ns('label-value-metadata')}>
21
+ {labelValuePairs.reduce(
22
+ (acc, labelValuePair, i) =>
23
+ acc.concat([
24
+ <Typography component="dt" key={`label-${i}`} variant="subtitle2">
25
+ {labelValuePair.label || defaultLabel}
26
+ </Typography>,
27
+ <Typography style={{ marginBottom: '.5em', marginLeft: '0px' }} component="dd" key={`value-${i}`} variant="body1">
28
+ <SanitizedHtml htmlString={labelValuePair.values.join(labelValueJoiner)} ruleSet="iiif" />
29
+ </Typography>,
30
+ ]),
31
+ [],
32
+ )}
33
+ </dl>
34
+ );
35
+ }
36
+
37
+ LabelValueMetadata.propTypes = {
38
+ defaultLabel: PropTypes.string,
39
+ labelValueJoiner: PropTypes.string,
40
+ labelValuePairs: PropTypes.arrayOf(
41
+ PropTypes.shape({
42
+ label: PropTypes.string,
43
+ values: PropTypes.arrayOf(PropTypes.string),
44
+ }),
45
+ ).isRequired,
46
+ };
@@ -0,0 +1,46 @@
1
+ import ListItemIcon from '@mui/material/ListItemIcon';
2
+ import ListItemText from '@mui/material/ListItemText';
3
+ import MenuItem from '@mui/material/MenuItem';
4
+ import CheckIcon from '@mui/icons-material/CheckSharp';
5
+ import PropTypes from 'prop-types';
6
+
7
+ /**
8
+ * LanguageSettings ~ the workspace sub menu to change the language
9
+ * of the application
10
+ */
11
+ export function LanguageSettings({ handleClick, languages }) {
12
+ return (
13
+ <>
14
+ {languages.map((language) => (
15
+ <MenuItem
16
+ aria-selected={language.current}
17
+ key={language.locale}
18
+ lang={language.locale}
19
+ onClick={() => {
20
+ handleClick(language.locale);
21
+ }}
22
+ >
23
+ <ListItemIcon>{language.current && <CheckIcon />}</ListItemIcon>
24
+ <ListItemText
25
+ slotProps={{
26
+ primary: { variant: 'body1' },
27
+ }}
28
+ >
29
+ {language.label}
30
+ </ListItemText>
31
+ </MenuItem>
32
+ ))}
33
+ </>
34
+ );
35
+ }
36
+
37
+ LanguageSettings.propTypes = {
38
+ handleClick: PropTypes.func.isRequired,
39
+ languages: PropTypes.arrayOf(
40
+ PropTypes.shape({
41
+ current: PropTypes.bool.isRequired,
42
+ label: PropTypes.string.isRequired,
43
+ locale: PropTypes.string.isRequired,
44
+ }),
45
+ ).isRequired,
46
+ };
@@ -0,0 +1,24 @@
1
+ import PropTypes from 'prop-types';
2
+ import { useTranslation } from 'react-i18next';
3
+ import CompanionWindow from '../containers/CompanionWindow';
4
+ import CanvasLayers from '../containers/CanvasLayers';
5
+
6
+ /**
7
+ * a panel showing the canvases for a given manifest
8
+ */
9
+ export function LayersPanel({ canvasIds = [], id, windowId }) {
10
+ const { t } = useTranslation();
11
+ return (
12
+ <CompanionWindow title={t('layers')} id={id} windowId={windowId}>
13
+ {canvasIds.map((canvasId, index) => (
14
+ <CanvasLayers canvasId={canvasId} index={index} key={canvasId} totalSize={canvasIds.length} windowId={windowId} />
15
+ ))}
16
+ </CompanionWindow>
17
+ );
18
+ }
19
+
20
+ LayersPanel.propTypes = {
21
+ canvasIds: PropTypes.arrayOf(PropTypes.string),
22
+ id: PropTypes.string.isRequired,
23
+ windowId: PropTypes.string.isRequired,
24
+ };
@@ -0,0 +1,56 @@
1
+ import PropTypes from 'prop-types';
2
+ import keyBy from 'lodash/keyBy';
3
+ import MenuItem from '@mui/material/MenuItem';
4
+ import InputLabel from '@mui/material/InputLabel';
5
+ import FormControl from '@mui/material/FormControl';
6
+ import Select from '@mui/material/Select';
7
+ import Typography from '@mui/material/Typography';
8
+ import { useSelector } from 'react-redux';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { getLanguagesFromConfigWithCurrent } from '../state/selectors/config';
11
+
12
+ /**
13
+ * Provide a locale picker
14
+ */
15
+ export function LocalePicker({ availableLocales = [], locale = '', setLocale = undefined }) {
16
+ const languages = useSelector((state) => getLanguagesFromConfigWithCurrent(state));
17
+ const { t } = useTranslation();
18
+
19
+ if (!setLocale || availableLocales.length < 2) return null;
20
+
21
+ const selectedLocale = availableLocales.indexOf(locale) >= 0 ? locale : availableLocales[0];
22
+ const labels = keyBy(languages, (o) => o.locale);
23
+
24
+ return (
25
+ <FormControl variant="standard">
26
+ <InputLabel>{t('language')}</InputLabel>
27
+ <Select
28
+ MenuProps={{
29
+ anchorOrigin: {
30
+ horizontal: 'left',
31
+ vertical: 'bottom',
32
+ },
33
+ }}
34
+ autoWidth
35
+ displayEmpty
36
+ value={selectedLocale}
37
+ onChange={(e) => {
38
+ setLocale(e.target.value);
39
+ }}
40
+ name="locale"
41
+ >
42
+ {availableLocales.map((l) => (
43
+ <MenuItem key={l} value={l}>
44
+ <Typography variant="body2">{labels[l]?.label || l}</Typography>
45
+ </MenuItem>
46
+ ))}
47
+ </Select>
48
+ </FormControl>
49
+ );
50
+ }
51
+
52
+ LocalePicker.propTypes = {
53
+ availableLocales: PropTypes.arrayOf(PropTypes.string),
54
+ locale: PropTypes.string,
55
+ setLocale: PropTypes.func,
56
+ };