@@ -134,12 +132,10 @@ export default connect(
'selectIpfsReady',
'selectShowTooltip',
'doFilesNavigateTo',
- 'doExploreUserProvidedPath',
'doUpdateUrl',
'doUpdateHash',
'doSetupLocalStorage',
'doTryInitIpfs',
- 'doInitHelia',
'doFilesWrite',
'doDisableTooltip',
'selectFilesPathInfo',
diff --git a/src/bundles/index.js b/src/bundles/index.js
index cdf9fa362..1021b308b 100644
--- a/src/bundles/index.js
+++ b/src/bundles/index.js
@@ -1,6 +1,5 @@
import { composeBundles, createCacheBundle } from 'redux-bundler'
import ipfsProvider from './ipfs-provider.js'
-import { exploreBundle, heliaBundle } from 'ipld-explorer-components'
import appIdle from './app-idle.js'
import nodeBandwidthChartBundle from './node-bandwidth-chart.js'
import nodeBandwidthBundle from './node-bandwidth.js'
@@ -32,13 +31,11 @@ export default composeBundles(
}),
appIdle({ idleTimeout: 5000 }),
ipfsProvider,
- heliaBundle,
identityBundle,
routesBundle,
redirectsBundle,
toursBundle,
filesBundle(),
- exploreBundle(),
configBundle,
configSaveBundle,
gatewayBundle,
diff --git a/src/bundles/routes.js b/src/bundles/routes.js
index b3c632ef4..6dd314578 100644
--- a/src/bundles/routes.js
+++ b/src/bundles/routes.js
@@ -2,17 +2,16 @@ import { createRouteBundle } from 'redux-bundler'
import StatusPage from '../status/LoadableStatusPage.js'
import FilesPage from '../files/LoadableFilesPage.js'
import PinsPage from '../pins/LoadablePinsPage.js'
-import StartExploringPage from '../explore/LoadableStartExploringPage.js'
-import ExplorePage from '../explore/LoadableExplorePage.js'
import PeersPage from '../peers/LoadablePeersPage.js'
import SettingsPage from '../settings/LoadableSettingsPage.js'
import AnalyticsPage from '../settings/AnalyticsPage.js'
import WelcomePage from '../welcome/LoadableWelcomePage.js'
import BlankPage from '../blank/BlankPage.js'
+import ExplorePageRenderer from '../explore/explore-page-renderer.jsx'
export default createRouteBundle({
- '/explore': StartExploringPage,
- '/explore*': ExplorePage,
+ '/explore': ExplorePageRenderer,
+ '/explore*': ExplorePageRenderer,
'/files*': FilesPage,
'/ipfs*': FilesPage,
'/ipns*': FilesPage,
diff --git a/src/components/analytics-banner/AnalyticsBanner.js b/src/components/analytics-banner/AnalyticsBanner.js
index 818bae4f5..f6bb9696d 100644
--- a/src/components/analytics-banner/AnalyticsBanner.js
+++ b/src/components/analytics-banner/AnalyticsBanner.js
@@ -1,5 +1,5 @@
import React from 'react'
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
const AnalyticsBanner = ({ className, label, yesLabel, onYes }) => {
return (
diff --git a/src/components/api-address-form/ApiAddressForm.js b/src/components/api-address-form/ApiAddressForm.js
index de3841cee..da30643ce 100644
--- a/src/components/api-address-form/ApiAddressForm.js
+++ b/src/components/api-address-form/ApiAddressForm.js
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import { connect } from 'redux-bundler-react'
import { withTranslation } from 'react-i18next'
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import { checkValidAPIAddress } from '../../bundles/ipfs-provider.js'
const ApiAddressForm = ({ t, doUpdateIpfsApiAddress, ipfsApiAddress, ipfsInitFailed }) => {
diff --git a/src/components/button/Button.js b/src/components/button/Button.js
deleted file mode 100644
index b995871aa..000000000
--- a/src/components/button/Button.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react'
-import './Button.css'
-import classNames from 'classnames'
-import PropTypes from 'prop-types'
-
-const getButtonClassName = ({ fill, bg, color, danger, disabled }, type) => {
- if (danger) return 'bg-red fill-white white'
- if (disabled) return 'bg-gray-muted fill-snow light-gray'
- if (type === 'link') return 'link bg-transparent'
- return `${fill} ${bg} ${color}`
-}
-
-const Button = ({ className, minWidth, children, style, type, danger, ...props }) =>
-
-
-Button.defaultProps = {
- bg: 'bg-teal',
- color: 'white',
- fill: 'white',
- className: '',
- minWidth: 140
-}
-
-Button.propTypes = {
- danger: PropTypes.bool,
- disabled: PropTypes.bool
-}
-
-export default Button
diff --git a/src/components/button/Button.css b/src/components/button/button.css
similarity index 100%
rename from src/components/button/Button.css
rename to src/components/button/button.css
diff --git a/src/components/button/Button.stories.js b/src/components/button/button.stories.js
similarity index 97%
rename from src/components/button/Button.stories.js
rename to src/components/button/button.stories.js
index cb0edf89f..087fa91b2 100644
--- a/src/components/button/Button.stories.js
+++ b/src/components/button/button.stories.js
@@ -1,7 +1,7 @@
import React from 'react'
import { action } from '@storybook/addon-actions'
-import Button from './Button.js'
+import Button from './button'
/**
* @type {import('@storybook/react').Meta}
diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx
new file mode 100644
index 000000000..aaab0a1f7
--- /dev/null
+++ b/src/components/button/button.tsx
@@ -0,0 +1,40 @@
+import React from 'react'
+import './button.css'
+import classNames from 'classnames'
+
+export interface ButtonProps extends Omit
, 'type'> {
+ minWidth?: number
+ children: React.ReactNode | React.ReactNode[]
+ type?: 'link'
+ buttonType?: React.ButtonHTMLAttributes['type']
+ danger?: boolean
+ fill?: string
+ bg?: string
+ color?: string
+}
+
+const getButtonClassName = ({ fill, bg, color, danger, disabled }: Pick, type: ButtonProps['type']) => {
+ if (danger) return 'bg-red fill-white white'
+ if (disabled) return 'bg-gray-muted fill-snow light-gray'
+ if (type === 'link') return 'link bg-transparent'
+ return `${fill} ${bg} ${color}`
+}
+
+const defaultProps: Partial = {
+ bg: 'bg-teal',
+ color: 'white',
+ fill: 'white',
+ className: '',
+ minWidth: 140
+}
+
+const Button: React.FC = (givenProps) => {
+ const { className, minWidth, children, style, buttonType, type, danger, ...props } = { ...defaultProps, ...givenProps }
+ return (
+
+ )
+}
+
+export default Button
diff --git a/src/components/cli-tutor-mode/CliTutorMode.js b/src/components/cli-tutor-mode/CliTutorMode.js
index cca503806..6c7ae41e1 100644
--- a/src/components/cli-tutor-mode/CliTutorMode.js
+++ b/src/components/cli-tutor-mode/CliTutorMode.js
@@ -5,7 +5,7 @@ import { connect } from 'redux-bundler-react'
// Components
import { Modal, ModalBody, ModalActions } from '../modal/Modal.js'
import StrokeCode from '../../icons/StrokeCode.js'
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import Overlay from '../overlay/Overlay.js'
import Shell from '../shell/Shell.js'
import StrokeDownload from '../../icons/StrokeDownload.js'
diff --git a/src/components/ipns-manager/IpnsManager.js b/src/components/ipns-manager/IpnsManager.js
index 871b99a6a..95c2075d3 100644
--- a/src/components/ipns-manager/IpnsManager.js
+++ b/src/components/ipns-manager/IpnsManager.js
@@ -4,7 +4,7 @@ import { sortByProperty } from '../../lib/sort.js'
import { AutoSizer, Table, Column, SortDirection } from 'react-virtualized'
// Components
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import Overlay from '../overlay/Overlay.js'
import GenerateKeyModal from './generate-key-modal/GenerateKeyModal.js'
import RenameKeyModal from './rename-key-modal/RenameKeyModal.js'
diff --git a/src/components/ipns-manager/remove-key-modal/RemoveKeyModal.js b/src/components/ipns-manager/remove-key-modal/RemoveKeyModal.js
index 9409daaba..0a622f3d9 100644
--- a/src/components/ipns-manager/remove-key-modal/RemoveKeyModal.js
+++ b/src/components/ipns-manager/remove-key-modal/RemoveKeyModal.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import TrashIcon from '../../../icons/StrokeTrash.js'
-import Button from '../../button/Button.js'
+import Button from '../../button/button.tsx'
import { Modal, ModalActions, ModalBody } from '../../modal/Modal.js'
import { Trans } from 'react-i18next'
diff --git a/src/components/language-selector/LanguageSelector.js b/src/components/language-selector/LanguageSelector.js
index 3518cffad..f24b7b391 100644
--- a/src/components/language-selector/LanguageSelector.js
+++ b/src/components/language-selector/LanguageSelector.js
@@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react'
import { getCurrentLanguage } from '../../lib/i18n.js'
// Components
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import Overlay from '../overlay/Overlay.js'
import LanguageModal from './language-modal/LanguageModal.js'
diff --git a/src/components/language-selector/language-modal/LanguageModal.js b/src/components/language-selector/language-modal/LanguageModal.js
index 6a1e63f68..f73074e33 100644
--- a/src/components/language-selector/language-modal/LanguageModal.js
+++ b/src/components/language-selector/language-modal/LanguageModal.js
@@ -6,7 +6,7 @@ import i18n, { localesList } from '../../../i18n.js'
// Components
import { Modal, ModalBody, ModalActions } from '../../modal/Modal.js'
import SpeakerIcon from '../../../icons/StrokeSpeaker.js'
-import Button from '../../button/Button.js'
+import Button from '../../button/button.tsx'
const LanguageModal = ({ t, tReady, onLeave, link, className, isIpfsDesktop, doDesktopUpdateLanguage, ...props }) => {
const handleClick = (lang) => {
diff --git a/src/components/pinning-manager/PinningManager.js b/src/components/pinning-manager/PinningManager.js
index eeba56218..0b420f328 100644
--- a/src/components/pinning-manager/PinningManager.js
+++ b/src/components/pinning-manager/PinningManager.js
@@ -4,7 +4,7 @@ import { AutoSizer, Table, Column, SortDirection } from 'react-virtualized'
import { sortByProperty } from '../../lib/sort.js'
// Components
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import Overlay from '../overlay/Overlay.js'
import PinningModal from './pinning-manager-modal/PinningManagerModal.js'
import AutoUploadModal from './auto-upload-modal/AutoUploadModal.js'
diff --git a/src/components/pinning-manager/auto-upload-modal/AutoUploadModal.js b/src/components/pinning-manager/auto-upload-modal/AutoUploadModal.js
index 601463355..930cfefa0 100644
--- a/src/components/pinning-manager/auto-upload-modal/AutoUploadModal.js
+++ b/src/components/pinning-manager/auto-upload-modal/AutoUploadModal.js
@@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next'
// Components
import { Modal, ModalBody, ModalActions } from '../../modal/Modal.js'
-import Button from '../../button/Button.js'
+import Button from '../../button/button.tsx'
const AutoUploadModal = ({ name, service, t, onLeave, doSetAutoUploadForService, className, ...props }) => {
const onToggle = () => {
diff --git a/src/components/pinning-manager/pinning-manager-modal/PinningManagerModal.js b/src/components/pinning-manager/pinning-manager-modal/PinningManagerModal.js
index a87644f3d..abdf17539 100644
--- a/src/components/pinning-manager/pinning-manager-modal/PinningManagerModal.js
+++ b/src/components/pinning-manager/pinning-manager-modal/PinningManagerModal.js
@@ -7,7 +7,7 @@ import './PinningManagerModal.css'
// Components
import { Modal, ModalBody, ModalActions } from '../../modal/Modal.js'
-import Button from '../../button/Button.js'
+import Button from '../../button/button.tsx'
import Overlay from '../../overlay/Overlay.js'
const PinningManagerModal = ({ t, tReady, onLeave, className, remoteServiceTemplates, pinningServicesDefaults, ...props }) => {
diff --git a/src/components/pinning-manager/pinning-manager-service-modal/PinningManagerServiceModal.js b/src/components/pinning-manager/pinning-manager-service-modal/PinningManagerServiceModal.js
index 413988ece..463df412f 100644
--- a/src/components/pinning-manager/pinning-manager-service-modal/PinningManagerServiceModal.js
+++ b/src/components/pinning-manager/pinning-manager-service-modal/PinningManagerServiceModal.js
@@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form'
// Components
import { Modal, ModalBody, ModalActions } from '../../modal/Modal.js'
-import Button from '../../button/Button.js'
+import Button from '../../button/button.tsx'
import './PinningManagerServiceModal.css'
const PinningManagerServiceModal = ({ t, onLeave, onSuccess, className, service, tReady, doAddPinningService, nickname, apiEndpoint, visitServiceUrl, secretApiKey, complianceReportUrl, ...props }) => {
diff --git a/src/components/public-gateway-form/PublicGatewayForm.js b/src/components/public-gateway-form/PublicGatewayForm.js
index b214e9f6c..35a0fff0d 100644
--- a/src/components/public-gateway-form/PublicGatewayForm.js
+++ b/src/components/public-gateway-form/PublicGatewayForm.js
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import { connect } from 'redux-bundler-react'
import { withTranslation } from 'react-i18next'
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import { checkValidHttpUrl, checkViaImgSrc, DEFAULT_PATH_GATEWAY } from '../../bundles/gateway.js'
const PublicGatewayForm = ({ t, doUpdatePublicGateway, publicGateway }) => {
diff --git a/src/components/public-subdomain-gateway-form/PublicSubdomainGatewayForm.js b/src/components/public-subdomain-gateway-form/PublicSubdomainGatewayForm.js
index 69d52c2fc..92620c23c 100644
--- a/src/components/public-subdomain-gateway-form/PublicSubdomainGatewayForm.js
+++ b/src/components/public-subdomain-gateway-form/PublicSubdomainGatewayForm.js
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import { connect } from 'redux-bundler-react'
import { withTranslation } from 'react-i18next'
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import { checkValidHttpUrl, checkSubdomainGateway, DEFAULT_SUBDOMAIN_GATEWAY } from '../../bundles/gateway.js'
const PublicSubdomainGatewayForm = ({ t, doUpdatePublicSubdomainGateway, publicSubdomainGateway }) => {
diff --git a/src/components/text-input-modal/TextInputModal.js b/src/components/text-input-modal/TextInputModal.js
index ba57c417f..5edbcb773 100644
--- a/src/components/text-input-modal/TextInputModal.js
+++ b/src/components/text-input-modal/TextInputModal.js
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from '../button/Button.js'
+import Button from '../button/button.tsx'
import { Modal, ModalActions, ModalBody } from '../modal/Modal.js'
import ComponentLoader from '../../loader/ComponentLoader.js'
import { withTranslation } from 'react-i18next'
diff --git a/src/explore/ExploreContainer.js b/src/explore/ExploreContainer.js
index 5a4449505..977715029 100644
--- a/src/explore/ExploreContainer.js
+++ b/src/explore/ExploreContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { connect } from 'redux-bundler-react'
-import { ExplorePage } from 'ipld-explorer-components'
+import { ExplorePage } from 'ipld-explorer-components/pages'
import withTour from '../components/tour/withTour.js'
const ExploreContainer = ({
@@ -8,16 +8,18 @@ const ExploreContainer = ({
handleJoyrideCallback,
availableGatewayUrl,
publicGateway
-}) => (
-
-
-
-)
+}) => {
+ return (
+
+
+
+ )
+}
export default connect(
'selectToursEnabled',
diff --git a/src/explore/StartExploringContainer.js b/src/explore/StartExploringContainer.js
index 9d3e50afe..5deef8022 100644
--- a/src/explore/StartExploringContainer.js
+++ b/src/explore/StartExploringContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { connect } from 'redux-bundler-react'
-import { StartExploringPage } from 'ipld-explorer-components'
+import { StartExploringPage } from 'ipld-explorer-components/pages'
import withTour from '../components/tour/withTour.js'
const StartExploringContainer = ({
diff --git a/src/explore/explore-page-renderer.jsx b/src/explore/explore-page-renderer.jsx
new file mode 100644
index 000000000..478caa32b
--- /dev/null
+++ b/src/explore/explore-page-renderer.jsx
@@ -0,0 +1,35 @@
+import React, { useEffect } from 'react'
+import { connect } from 'redux-bundler-react'
+import LoadableExplorePage from './LoadableExplorePage'
+import LoadableStartExploringPage from './LoadableStartExploringPage'
+import { useExplore, useHelia } from 'ipld-explorer-components/providers'
+import 'ipld-explorer-components/css'
+
+const ExplorePageRenderer = ({ routeInfo }) => {
+ const { pattern, url } = routeInfo
+ const { setExplorePath } = useExplore()
+ const { doInitHelia, helia } = useHelia()
+
+ useEffect(() => {
+ if (helia == null) {
+ doInitHelia()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [helia])
+
+ useEffect(() => {
+ setExplorePath(window.location.hash)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [url])
+
+ if (pattern === '/explore') {
+ return
+ }
+
+ return
+}
+
+export default connect(
+ 'selectRouteInfo',
+ ExplorePageRenderer
+)
diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js
index 0f7f32976..1692ec486 100644
--- a/src/files/FilesPage.js
+++ b/src/files/FilesPage.js
@@ -18,13 +18,15 @@ import { getJoyrideLocales } from '../helpers/i8n.js'
import Modals, { DELETE, NEW_FOLDER, SHARE, RENAME, ADD_BY_PATH, CLI_TUTOR_MODE, PINNING, PUBLISH } from './modals/Modals.js'
import Header from './header/Header.js'
import FileImportStatus from './file-import-status/FileImportStatus.js'
+import { useExplore } from 'ipld-explorer-components/providers'
const FilesPage = ({
doFetchPinningServices, doFilesFetch, doPinsFetch, doFilesSizeGet, doFilesDownloadLink, doFilesDownloadCarLink, doFilesWrite, doFilesAddPath, doUpdateHash,
- doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, pendingPins, failedPins, doExploreUserProvidedPath,
+ doFilesUpdateSorting, doFilesNavigateTo, doFilesMove, doSetCliOptions, doFetchRemotePins, remotePins, pendingPins, failedPins,
ipfsProvider, ipfsConnected, doFilesMakeDir, doFilesShareLink, doFilesDelete, doSetPinning, onRemotePinClick, doPublishIpnsKey,
files, filesPathInfo, pinningServices, toursEnabled, handleJoyrideCallback, isCliTutorModeEnabled, cliOptions, t
}) => {
+ const { doExploreUserProvidedPath } = useExplore()
const contextMenuRef = useRef()
const [modals, setModals] = useState({ show: null, files: null })
const [contextMenu, setContextMenu] = useState({
@@ -277,7 +279,6 @@ export default connect(
'doFilesWrite',
'doFilesDownloadLink',
'doFilesDownloadCarLink',
- 'doExploreUserProvidedPath',
'doFilesSizeGet',
'selectIsCliTutorModeEnabled',
'selectIsCliTutorModalOpen',
diff --git a/src/files/explore-form/FilesExploreForm.js b/src/files/explore-form/FilesExploreForm.js
deleted file mode 100644
index 570640dbf..000000000
--- a/src/files/explore-form/FilesExploreForm.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from 'react'
-import * as isIPFS from 'is-ipfs'
-import PropTypes from 'prop-types'
-import { withTranslation } from 'react-i18next'
-import StrokeFolder from '../../icons/StrokeFolder.js'
-import StrokeIpld from '../../icons/StrokeIpld.js'
-import Button from '../../components/button/Button.js'
-import './FilesExploreForm.css'
-
-class FilesExploreForm extends React.Component {
- constructor (props) {
- super(props)
- this.state = {
- path: '',
- hideExplore: false
- }
- this.onChange = this.onChange.bind(this)
- this.onKeyDown = this.onKeyDown.bind(this)
- this.onBrowse = this.onBrowse.bind(this)
- this.onInspect = this.onInspect.bind(this)
- }
-
- onKeyDown (evt) {
- if (evt.key === 'Enter') {
- this.onBrowse(evt)
- }
- }
-
- onBrowse (evt) {
- evt.preventDefault()
-
- if (this.isValid) {
- let path = this.path
-
- if (isIPFS.cid(path)) {
- path = `/ipfs/${path}`
- }
-
- this.props.onBrowse({ path })
- this.setState({ path: '' })
- }
- }
-
- onInspect (evt) {
- evt.preventDefault()
-
- if (this.isValid) {
- this.props.onInspect(this.path)
- this.setState({ path: '' })
- }
- }
-
- onChange (evt) {
- const path = evt.target.value
- this.setState({ path })
- }
-
- get path () {
- return this.state.path.trim()
- }
-
- get isValid () {
- return this.path !== '' && (isIPFS.cid(this.path) || isIPFS.path(this.path))
- }
-
- get inputClass () {
- if (this.path === '') {
- return 'focus-outline'
- }
-
- if (this.isValid) {
- return 'b--green-muted focus-outline-green'
- } else {
- return 'b--red-muted focus-outline-red'
- }
- }
-
- render () {
- const { t } = this.props
- return (
-
-
-
-
- Paste in a CID or IPFS path
-
-
-
-
-
-
-
- )
- }
-}
-
-FilesExploreForm.propTypes = {
- t: PropTypes.func.isRequired,
- onInspect: PropTypes.func.isRequired,
- onBrowse: PropTypes.func.isRequired
-}
-
-export default withTranslation('files')(FilesExploreForm)
diff --git a/src/files/explore-form/FilesExploreForm.css b/src/files/explore-form/files-explore-form.css
similarity index 100%
rename from src/files/explore-form/FilesExploreForm.css
rename to src/files/explore-form/files-explore-form.css
diff --git a/src/files/explore-form/FilesExploreForm.stories.js b/src/files/explore-form/files-explore-form.stories.js
similarity index 85%
rename from src/files/explore-form/FilesExploreForm.stories.js
rename to src/files/explore-form/files-explore-form.stories.js
index f9004630f..ed65942e5 100644
--- a/src/files/explore-form/FilesExploreForm.stories.js
+++ b/src/files/explore-form/files-explore-form.stories.js
@@ -1,5 +1,5 @@
// @ts-check
-import FilesExploreForm from './FilesExploreForm.js'
+import FilesExploreForm from './files-explore-form'
/**
* @type {import('@storybook/react').Meta}
diff --git a/src/files/explore-form/files-explore-form.tsx b/src/files/explore-form/files-explore-form.tsx
new file mode 100644
index 000000000..d45f1c236
--- /dev/null
+++ b/src/files/explore-form/files-explore-form.tsx
@@ -0,0 +1,107 @@
+import React, { useCallback, useMemo, useState } from 'react'
+import * as isIPFS from 'is-ipfs'
+import { useTranslation } from 'react-i18next'
+import StrokeFolder from '../../icons/StrokeFolder.js'
+import StrokeIpld from '../../icons/StrokeIpld.js'
+import Button from '../../components/button/button'
+import './files-explore-form.css'
+// @ts-expect-error - need to fix types for ipfs-webui since we are a CJS consumer...
+import { useExplore } from 'ipld-explorer-components/providers'
+
+/**
+ * @type {React.FC<{ onBrowse: (evt: { path: string }) => void }>} *
+ */
+const FilesExploreForm = ({ onBrowse: onBrowseProp }) => {
+ const [path, setPath] = useState('')
+ const { doExploreUserProvidedPath } = useExplore()
+ const { t } = useTranslation('files')
+
+ const trimmedPath = useMemo(() => {
+ return path.trim()
+ }, [path])
+
+ const isValid = useMemo(() => {
+ return trimmedPath !== '' && (isIPFS.cid(trimmedPath) || isIPFS.path(trimmedPath))
+ }, [trimmedPath])
+
+ const inputClass = useMemo(() => {
+ if (trimmedPath === '') {
+ return 'focus-outline'
+ }
+
+ if (isValid) {
+ return 'b--green-muted focus-outline-green'
+ } else {
+ return 'b--red-muted focus-outline-red'
+ }
+ }, [trimmedPath, isValid])
+
+ const onChange = (evt) => {
+ setPath(evt.target.value)
+ }
+ const onKeyDown = (evt) => {
+ if (evt.key === 'Enter') {
+ onBrowse(evt)
+ }
+ }
+ const onInspect = useCallback((evt) => {
+ evt.preventDefault()
+
+ if (isValid) {
+ doExploreUserProvidedPath(trimmedPath)
+ setPath('')
+ }
+ }, [doExploreUserProvidedPath, isValid, trimmedPath])
+
+ const onBrowse = useCallback((evt) => {
+ evt.preventDefault()
+
+ if (isValid) {
+ let browsePath = trimmedPath
+ if (isIPFS.cid(trimmedPath)) {
+ browsePath = `/ipfs/${trimmedPath}`
+ }
+
+ onBrowseProp({ path: browsePath })
+ setPath('')
+ }
+ }, [isValid, trimmedPath, onBrowseProp])
+
+ return (
+
+
+
+
+ Paste in a CID or IPFS path
+
+
+
+
+
+
+
+ )
+}
+
+export default FilesExploreForm
diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js
index 2ce7a7d03..c83ddfbc1 100644
--- a/src/files/file-input/FileInput.js
+++ b/src/files/file-input/FileInput.js
@@ -10,7 +10,7 @@ import NewFolderIcon from '../../icons/StrokeNewFolder.js'
import DecentralizationIcon from '../../icons/StrokeDecentralization.js'
// Components
import { Dropdown, DropdownMenu, Option } from '../dropdown/Dropdown.js'
-import Button from '../../components/button/Button.js'
+import Button from '../../components/button/button.tsx'
import { cliCmdKeys } from '../../bundles/files/consts.js'
const AddButton = withTranslation('app')(
diff --git a/src/files/file-preview/FilePreview.js b/src/files/file-preview/FilePreview.js
index 58b5f40ea..03513eb13 100644
--- a/src/files/file-preview/FilePreview.js
+++ b/src/files/file-preview/FilePreview.js
@@ -9,7 +9,7 @@ import './FilePreview.css'
import { CID } from 'multiformats/cid'
import { useDrag } from 'react-dnd'
import { toString as fromUint8ArrayToString } from 'uint8arrays'
-import Button from '../../components/button/Button.js'
+import Button from '../../components/button/button.tsx'
const maxPlainTextPreview = 1024 * 10 // only preview small part of huge files
diff --git a/src/files/header/Header.js b/src/files/header/Header.js
index 323b97603..36298d287 100644
--- a/src/files/header/Header.js
+++ b/src/files/header/Header.js
@@ -6,7 +6,7 @@ import { humanSize } from '../../lib/files.js'
// Components
import Breadcrumbs from '../breadcrumbs/Breadcrumbs.js'
import FileInput from '../file-input/FileInput.js'
-import Button from '../../components/button/Button.js'
+import Button from '../../components/button/button.tsx'
// Icons
import GlyphDots from '../../icons/GlyphDots.js'
import GlyphPinCloud from '../../icons/GlyphPinCloud.js'
diff --git a/src/files/modals/add-by-path-modal/AddByPathModal.js b/src/files/modals/add-by-path-modal/AddByPathModal.js
index 460a4e1ee..251fada86 100644
--- a/src/files/modals/add-by-path-modal/AddByPathModal.js
+++ b/src/files/modals/add-by-path-modal/AddByPathModal.js
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from '../../../components/button/Button.js'
+import Button from '../../../components/button/button.tsx'
import { Modal, ModalActions, ModalBody } from '../../../components/modal/Modal.js'
import { withTranslation } from 'react-i18next'
import * as isIPFS from 'is-ipfs'
diff --git a/src/files/modals/pinning-modal/PinningModal.js b/src/files/modals/pinning-modal/PinningModal.js
index c5a9e87b1..a18a006fb 100644
--- a/src/files/modals/pinning-modal/PinningModal.js
+++ b/src/files/modals/pinning-modal/PinningModal.js
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import { /* Trans, */ withTranslation } from 'react-i18next'
import { humanSize } from '../../../lib/files.js'
-import Button from '../../../components/button/Button.js'
+import Button from '../../../components/button/button.tsx'
import Checkbox from '../../../components/checkbox/Checkbox.js'
import GlyphPin from '../../../icons/GlyphPin.js'
import Icon from '../../../icons/StrokePinCloud.js'
diff --git a/src/files/modals/publish-modal/PublishModal.js b/src/files/modals/publish-modal/PublishModal.js
index 3734c2d5d..79349a82b 100644
--- a/src/files/modals/publish-modal/PublishModal.js
+++ b/src/files/modals/publish-modal/PublishModal.js
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { withTranslation } from 'react-i18next'
import { CopyToClipboard } from 'react-copy-to-clipboard'
-import Button from '../../../components/button/Button.js'
+import Button from '../../../components/button/button.tsx'
import { Modal, ModalActions, ModalBody } from '../../../components/modal/Modal.js'
import Icon from '../../../icons/StrokeSpeaker.js'
import { connect } from 'redux-bundler-react'
diff --git a/src/files/modals/remove-modal/RemoveModal.js b/src/files/modals/remove-modal/RemoveModal.js
index 47be25c47..8ae6dff5e 100644
--- a/src/files/modals/remove-modal/RemoveModal.js
+++ b/src/files/modals/remove-modal/RemoveModal.js
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { withTranslation } from 'react-i18next'
import TrashIcon from '../../../icons/StrokeTrash.js'
-import Button from '../../../components/button/Button.js'
+import Button from '../../../components/button/button.tsx'
import Checkbox from '../../../components/checkbox/Checkbox.js'
import { Modal, ModalActions, ModalBody } from '../../../components/modal/Modal.js'
import { connect } from 'redux-bundler-react'
diff --git a/src/files/modals/share-modal/ShareModal.js b/src/files/modals/share-modal/ShareModal.js
index 5e776da8b..5e713bf5f 100644
--- a/src/files/modals/share-modal/ShareModal.js
+++ b/src/files/modals/share-modal/ShareModal.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import ShareIcon from '../../../icons/StrokeShare.js'
-import Button from '../../../components/button/Button.js'
+import Button from '../../../components/button/button.tsx'
import { withTranslation } from 'react-i18next'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import { Modal, ModalActions, ModalBody } from '../../../components/modal/Modal.js'
diff --git a/src/i18n.test.js b/src/i18n.test.js
index 455034f3d..d45e7c0e1 100644
--- a/src/i18n.test.js
+++ b/src/i18n.test.js
@@ -4,8 +4,6 @@ import { createServer } from 'http-server'
import i18n, { localesList } from './i18n.js'
import getPort from 'get-port'
-const backendListenerPort = await getPort({ port: getPort.makeRange(3000, 4000) })
-
const allLanguages = localesList.map(({ locale }) => locale)
/**
@@ -13,6 +11,7 @@ const allLanguages = localesList.map(({ locale }) => locale)
*/
let httpServer
beforeAll(async function () {
+ const backendListenerPort = await getPort({ port: getPort.makeRange(3000, 4000) })
httpServer = createServer({
root: './public',
cors: true
diff --git a/src/index.js b/src/index.js
index c29da8682..52641f187 100644
--- a/src/index.js
+++ b/src/index.js
@@ -10,6 +10,7 @@ import { I18nextProvider } from 'react-i18next'
import i18n from './i18n.js'
import { DndProvider } from 'react-dnd'
import DndBackend from './lib/dnd-backend.js'
+import { HeliaProvider, ExploreProvider } from 'ipld-explorer-components/providers'
const appVersion = process.env.REACT_APP_VERSION
const gitRevision = process.env.REACT_APP_GIT_REV
@@ -26,7 +27,11 @@ async function render () {
-
+
+
+
+
+
,
diff --git a/src/lib/i18n.test.js b/src/lib/i18n.test.js
index 3009fafe4..29ecd9686 100644
--- a/src/lib/i18n.test.js
+++ b/src/lib/i18n.test.js
@@ -9,11 +9,13 @@ const testEachLanguage = (fn) => {
Object.keys(languages).forEach((lang) => fn(lang))
}
-const allLanguages = (await readdir('./public/locales', { withFileTypes: true }))
- .filter(dirent => dirent.isDirectory())
- .map(dirent => dirent.name)
-
describe('i18n', function () {
+ let allLanguages
+ beforeAll(async function () {
+ allLanguages = (await readdir('./public/locales', { withFileTypes: true }))
+ .filter(dirent => dirent.isDirectory())
+ .map(dirent => dirent.name)
+ })
it('should have a languages.json entry for each folder', function () {
const namedLocales = localesList.map(({ locale }) => locale)
expect(namedLocales).toEqual(allLanguages)
diff --git a/src/loader/AsyncRequestLoader.js b/src/loader/AsyncRequestLoader.js
deleted file mode 100644
index 59070b958..000000000
--- a/src/loader/AsyncRequestLoader.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import { connect } from 'redux-bundler-react'
-import debounceImport from 'react-debounce-render'
-import { Loader } from './Loader.js'
-const debounce = debounceImport.default
-
-export const AsyncRequestLoader = ({ asyncActive }) => (
-
-
-
-)
-
-const debouncedComponent = debounce(AsyncRequestLoader, 1000, { leading: true })
-
-export default connect('selectAsyncActive', debouncedComponent)
diff --git a/src/loader/AsyncRequestLoader.test.js b/src/loader/AsyncRequestLoader.test.js
deleted file mode 100644
index 35a1f8e73..000000000
--- a/src/loader/AsyncRequestLoader.test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* global it expect */
-import React from 'react'
-import enzyme from 'enzyme'
-import { AsyncRequestLoader } from './AsyncRequestLoader.js'
-const { shallow } = enzyme
-
-it('hidden if there are no async requests', () => {
- const wrapper = shallow(
-
- )
- expect(wrapper.find('.o-0').exists()).toBe(true)
-})
-
-it('visible if there are pending requests', () => {
- const wrapper = shallow(
-
- )
- expect(wrapper.find('.o-100').exists()).toBe(true)
-})
diff --git a/src/peers/AddConnection/AddConnection.js b/src/peers/AddConnection/AddConnection.js
index 8733dd1e5..c33dcfc15 100644
--- a/src/peers/AddConnection/AddConnection.js
+++ b/src/peers/AddConnection/AddConnection.js
@@ -6,7 +6,7 @@ import { P2P, Circuit, WebRTC } from '@multiformats/multiaddr-matcher'
import Checkbox from '../../components/checkbox/Checkbox.js'
import Icon from '../../icons/StrokeDecentralization.js'
-import Button from '../../components/button/Button.js'
+import Button from '../../components/button/button.tsx'
import Overlay from '../../components/overlay/Overlay.js'
import ComponentLoader from '../../loader/ComponentLoader.js'
diff --git a/src/settings/SettingsPage.js b/src/settings/SettingsPage.js
index aee9927f4..196a6db02 100644
--- a/src/settings/SettingsPage.js
+++ b/src/settings/SettingsPage.js
@@ -10,7 +10,7 @@ import { getJoyrideLocales } from '../helpers/i8n.js'
// Components
import Tick from '../icons/GlyphSmallTick.js'
import Box from '../components/box/Box.js'
-import Button from '../components/button/Button.js'
+import Button from '../components/button/button.tsx'
import LanguageSelector from '../components/language-selector/LanguageSelector.js'
import PinningManager from '../components/pinning-manager/PinningManager.js'
import IpnsManager from '../components/ipns-manager/IpnsManager.js'
diff --git a/src/setupTests.js b/src/setupTests.js
index 712095323..5352b276c 100644
--- a/src/setupTests.js
+++ b/src/setupTests.js
@@ -1,10 +1,9 @@
import * as enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
-import { createRequire } from 'module'
+import { TextDecoder, TextEncoder } from 'util'
const { configure } = enzyme.default
configure({ adapter: new Adapter() })
-const require = createRequire(import.meta.url)
-globalThis.TextDecoder = global.TextDecoder = require('util').TextDecoder
-globalThis.TextEncoder = global.TextEncoder = require('util').TextEncoder
+globalThis.TextDecoder = global.TextDecoder = TextDecoder
+globalThis.TextEncoder = global.TextEncoder = TextEncoder
diff --git a/test/e2e/playwright.config.js b/test/e2e/playwright.config.js
index a0012acd7..84a8103c9 100644
--- a/test/e2e/playwright.config.js
+++ b/test/e2e/playwright.config.js
@@ -3,7 +3,7 @@ import { defineConfig } from '@playwright/test'
import getPort from 'aegir/get-port'
const webuiPort = 3001
-const rpcPort = await getPort(55001, '0.0.0.0')
+const rpcPort = await getPort(5001, '0.0.0.0')
/** @type {import('@playwright/test').Config} */
const config = {
diff --git a/test/e2e/setup/ipfs-backend.js b/test/e2e/setup/ipfs-backend.js
index 41dac1ac4..ae511e576 100644
--- a/test/e2e/setup/ipfs-backend.js
+++ b/test/e2e/setup/ipfs-backend.js
@@ -15,7 +15,7 @@ const { console } = windowOrGlobal
let ipfsd
let ipfs
async function run (rpcPort) {
- let gatewayPort = 8998
+ let gatewayPort = 8080
if (ipfsd != null && ipfs != null) {
throw new Error('IPFS backend already running')
}
@@ -25,7 +25,7 @@ async function run (rpcPort) {
ipfs = create(endpoint)
} else {
// use ipfds-ctl to spawn daemon to expose http api used for e2e tests
- gatewayPort = await getPort(8998, '0.0.0.0')
+ gatewayPort = await getPort(gatewayPort, '0.0.0.0')
const factory = createFactory({
rpc: create,
type: 'kubo',