diff --git a/@types/internal-nav-helper/index.d.ts b/@types/internal-nav-helper/index.d.ts new file mode 100644 index 000000000..21f2ea17a --- /dev/null +++ b/@types/internal-nav-helper/index.d.ts @@ -0,0 +1,4 @@ +declare module 'internal-nav-helper' { + export function getNavHelper(onInternalNav: (path: string) => void): (e: MouseEvent) => void + export function findAnchorTag(el: HTMLElement): HTMLElement | null +} diff --git a/@types/loadable__component/index.d.ts b/@types/loadable__component/index.d.ts new file mode 100644 index 000000000..403dd04a1 --- /dev/null +++ b/@types/loadable__component/index.d.ts @@ -0,0 +1,15 @@ +declare module '@loadable/component' { + import React from 'react' + + interface LoadableOptions { + fallback?: React.ReactNode + ssr?: boolean + } + + function Loadable

( + loadFn: () => Promise<{ default: React.ComponentType

}>, + options?: LoadableOptions + ): React.ComponentType

+ + export default Loadable +} diff --git a/@types/react-helmet/index.d.ts b/@types/react-helmet/index.d.ts new file mode 100644 index 000000000..7130799e3 --- /dev/null +++ b/@types/react-helmet/index.d.ts @@ -0,0 +1,19 @@ +declare module 'react-helmet' { + import React from 'react' + + export interface HelmetProps { + children?: React.ReactNode + title?: string + meta?: Array<{ + name?: string + content?: string + property?: string + }> + link?: Array<{ + rel?: string + href?: string + }> + } + + export class Helmet extends React.Component {} +} diff --git a/@types/react-joyride/index.d.ts b/@types/react-joyride/index.d.ts new file mode 100644 index 000000000..7caaad488 --- /dev/null +++ b/@types/react-joyride/index.d.ts @@ -0,0 +1,24 @@ +import { Step } from 'react-joyride' + +declare module 'react-joyride' { + import type { TFunction } from 'i18next' + import type { Trans } from 'react-i18next' + + /** + * Custom Tour interface that extends the official types + * to include a getSteps function with translation support + */ + export interface CustomTour { + getSteps: (props: { + t: TFunction + Trans?: typeof Trans + }) => Step[] + styles: { + options?: Record + tooltip?: Record + tooltipContent?: Record + tooltipFooter?: Record + [key: string]: any + } + } +} diff --git a/@types/redux-bundler-react/index.d.ts b/@types/redux-bundler-react/index.d.ts new file mode 100644 index 000000000..93581c710 --- /dev/null +++ b/@types/redux-bundler-react/index.d.ts @@ -0,0 +1,28 @@ +declare module 'redux-bundler-react' { + import type { ComponentType, ReactNode } from 'react' + + // Overloaded function signatures for connect + export function connect>( + ...selectors: string[] + ): >(component: T) => T + + export function connect>( + selectors: string[], + component: ComponentType + ): ComponentType + + export function connect>( + selector: string, + component: ComponentType + ): ComponentType + + // Handle multiple selectors with a component + export function connect>( + ...args: [...string[], ComponentType] + ): ComponentType + + export class Provider extends ComponentType<{ + store: Record + children: ReactNode + }> {} +} diff --git a/src/bundles/files/actions.js b/src/bundles/files/actions.js index c5ed8bf01..381bd3fca 100644 --- a/src/bundles/files/actions.js +++ b/src/bundles/files/actions.js @@ -14,7 +14,7 @@ import { IGNORED_FILES, ACTIONS } from './consts.js' /** * @typedef {import('ipfs').IPFSService} IPFSService - * @typedef {import('../../lib/files').FileStream} FileStream + * @typedef {import('../../files/types').FileStream} FileStream * @typedef {import('./utils').Info} Info * @typedef {import('ipfs').Pin} Pin */ diff --git a/src/bundles/files/utils.js b/src/bundles/files/utils.js index a466586e4..bedf575d7 100644 --- a/src/bundles/files/utils.js +++ b/src/bundles/files/utils.js @@ -5,7 +5,7 @@ import { debouncedProvide } from '../../lib/files.js' /** * @typedef {import('ipfs').IPFSService} IPFSService - * @typedef {import('../../lib/files').FileStream} FileStream + * @typedef {import('../../files/types').FileStream} FileStream * @typedef {import('./actions').Ext} Ext * @typedef {import('./actions').Extra} Extra * @typedef {import('multiformats/cid').CID} CID diff --git a/src/components/about-ipfs/AboutIpfs.js b/src/components/about-ipfs/AboutIpfs.js index db7e8b9c8..e7fb064af 100644 --- a/src/components/about-ipfs/AboutIpfs.js +++ b/src/components/about-ipfs/AboutIpfs.js @@ -2,6 +2,10 @@ import React from 'react' import { withTranslation, Trans } from 'react-i18next' import Box from '../box/Box.js' +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + */ export const AboutIpfs = ({ t }) => { return ( diff --git a/src/components/about-webui/AboutWebUI.js b/src/components/about-webui/AboutWebUI.js index 8bcb81a01..06a942324 100644 --- a/src/components/about-webui/AboutWebUI.js +++ b/src/components/about-webui/AboutWebUI.js @@ -2,6 +2,10 @@ import React from 'react' import { withTranslation, Trans } from 'react-i18next' import Box from '../box/Box.js' +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + */ export const AboutWebUI = ({ t }) => { return ( diff --git a/src/components/box/Box.js b/src/components/box/Box.js index 7d044f1af..58bdd361e 100644 --- a/src/components/box/Box.js +++ b/src/components/box/Box.js @@ -1,11 +1,16 @@ import React from 'react' import ErrorBoundary from '../error/error-boundary.js' +/** + * @param {Object} props + * @param {string} [props.className] + * @param {React.CSSProperties} [props.style] + * @param {React.ReactNode} props.children + */ export const Box = ({ className = 'pa4', style, - children, - ...props + children }) => { return (

diff --git a/src/components/is-connected/IsConnected.js b/src/components/is-connected/IsConnected.js index ca47f5c84..4748caafd 100644 --- a/src/components/is-connected/IsConnected.js +++ b/src/components/is-connected/IsConnected.js @@ -3,6 +3,10 @@ import { withTranslation } from 'react-i18next' import Box from '../box/Box.js' import GlyphTick from '../../icons/GlyphTick.js' +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + */ export const IsConnected = ({ t }) => { return ( diff --git a/src/components/is-not-connected/IsNotConnected.js b/src/components/is-not-connected/IsNotConnected.js index 1c6b7851a..e9e06a1e0 100644 --- a/src/components/is-not-connected/IsNotConnected.js +++ b/src/components/is-not-connected/IsNotConnected.js @@ -13,7 +13,14 @@ const TABS = { WINDOWS: 'windowsCMD' } -const IsNotConnected = ({ t, apiUrl, connected, sameOrigin, ipfsApiAddress, doUpdateIpfsApiAddress }) => { +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + * @param {string} props.apiUrl + * @param {boolean} props.connected + * @param {boolean} props.sameOrigin + */ +const IsNotConnected = ({ t, apiUrl: _apiUrl, connected: _connected, sameOrigin }) => { const [activeTab, setActiveTab] = useState(TABS.UNIX) const defaultDomains = ['http://localhost:3000', 'http://127.0.0.1:5001', 'https://webui.ipfs.io'] const origin = window.location.origin @@ -79,10 +86,7 @@ const IsNotConnected = ({ t, apiUrl, connected, sameOrigin, ipfsApiAddress, doUp
  • Is your Kubo RPC on a port other than 5001? If your node is configured with a custom RPC API address, enter it here.
  • - +
    ) diff --git a/src/components/shell/Shell.js b/src/components/shell/Shell.js index e9dca171d..3b50accc3 100644 --- a/src/components/shell/Shell.js +++ b/src/components/shell/Shell.js @@ -1,6 +1,12 @@ import React from 'react' import classNames from 'classnames' +/** + * @param {Object} props + * @param {string} [props.title] + * @param {React.ReactNode} props.children + * @param {string} [props.className] + */ const Shell = ({ title = 'Shell', children, diff --git a/src/components/tour/withTour.js b/src/components/tour/withTour.js index fc27289eb..e2316d822 100644 --- a/src/components/tour/withTour.js +++ b/src/components/tour/withTour.js @@ -2,13 +2,20 @@ import React from 'react' import { connect } from 'redux-bundler-react' import { STATUS } from 'react-joyride' +/** + * @param {React.ComponentType} WrappedComponent + * @returns {React.ComponentType} + */ const withTour = WrappedComponent => { class WithTour extends React.Component { + /** + * @param {import('react-joyride').CallBackProps} data + */ handleJoyrideCallback = (data) => { const { doDisableTours } = this.props const { action, status } = data - if (action === 'close' || [STATUS.FINISHED].includes(status)) { + if (action === 'close' || status === STATUS.FINISHED) { doDisableTours() } } diff --git a/src/constants/pinning.js b/src/constants/pinning.js index cbac5e46e..7e1d48819 100644 --- a/src/constants/pinning.js +++ b/src/constants/pinning.js @@ -17,7 +17,16 @@ const complianceReportsHomepage = 'https://ipfs-shipyard.github.io/pinning-servi */ /** - * @type {PinningServiceTemplate[]} + * @typedef {object} PinningServiceTemplateWithCompliance + * @property {string} name + * @property {string} icon + * @property {string} apiEndpoint + * @property {string} visitServiceUrl + * @property {string} [complianceReportUrl] + */ + +/** + * @type {PinningServiceTemplateWithCompliance[]} */ const pinningServiceTemplates = [ { @@ -45,14 +54,18 @@ const pinningServiceTemplates = [ visitServiceUrl: 'https://docs.4everland.org/storage/4ever-pin/pinning-services-api' } ].map((service) => { + let complianceReportUrl try { const domain = new URL(service.apiEndpoint).hostname - service.complianceReportUrl = `${complianceReportsHomepage}/${domain}.html` + complianceReportUrl = `${complianceReportsHomepage}/${domain}.html` } catch (e) { // if apiEndpoint is not a valid URL, don't add complianceReportUrl // TODO: fix support for template apiEndpoints } - return { service, sort: Math.random() } + return { + service: { ...service, complianceReportUrl }, + sort: Math.random() + } }).sort((a, b) => a.sort - b.sort).map(({ service }) => service) export { diff --git a/src/contexts/identity-context.tsx b/src/contexts/identity-context.tsx index 9b93fffd6..45acc1b0c 100644 --- a/src/contexts/identity-context.tsx +++ b/src/contexts/identity-context.tsx @@ -151,7 +151,7 @@ const IdentityProviderImpl: React.FC = ({ children }) => }, [ipfsConnected, fetchIdentity, state.isLoading, state.identity, state.lastSuccess]) useEffect(() => { - if (!shouldPoll || !ipfsConnected || !state.lastSuccess) return + if (!shouldPoll || !ipfsConnected || !state.lastSuccess) return () => {} const REFRESH_INTERVAL = 5000 const timeSinceLastSuccess = Date.now() - state.lastSuccess @@ -162,6 +162,7 @@ const IdentityProviderImpl: React.FC = ({ children }) => } else { fetchIdentity() } + return () => {} }, [shouldPoll, ipfsConnected, state.lastSuccess, fetchIdentity]) const contextValue: IdentityContextValue = useMemo(() => ({ diff --git a/src/files/explore-form/files-explore-form.tsx b/src/files/explore-form/files-explore-form.tsx index d45f1c236..fd1b37b7f 100644 --- a/src/files/explore-form/files-explore-form.tsx +++ b/src/files/explore-form/files-explore-form.tsx @@ -8,10 +8,12 @@ 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 }) => { +interface FilesExploreFormProps { + // this prop is being passed as the `doFilesNavigateTo` action from the `files` bundle in App.js + onBrowse: ({ path, cid }: {path: string, cid?: string}) => void +} + +const FilesExploreForm: React.FC = ({ onBrowse: onBrowseProp }) => { const [path, setPath] = useState('') const { doExploreUserProvidedPath } = useExplore() const { t } = useTranslation('files') @@ -36,15 +38,15 @@ const FilesExploreForm = ({ onBrowse: onBrowseProp }) => { } }, [trimmedPath, isValid]) - const onChange = (evt) => { + const onChange: React.ChangeEventHandler = (evt) => { setPath(evt.target.value) } - const onKeyDown = (evt) => { + const onKeyDown: React.KeyboardEventHandler = (evt) => { if (evt.key === 'Enter') { onBrowse(evt) } } - const onInspect = useCallback((evt) => { + const onInspect = useCallback((evt: React.MouseEvent) => { evt.preventDefault() if (isValid) { @@ -53,16 +55,18 @@ const FilesExploreForm = ({ onBrowse: onBrowseProp }) => { } }, [doExploreUserProvidedPath, isValid, trimmedPath]) - const onBrowse = useCallback((evt) => { + const onBrowse = useCallback((evt: React.KeyboardEvent | React.MouseEvent) => { evt.preventDefault() if (isValid) { let browsePath = trimmedPath + let cid if (isIPFS.cid(trimmedPath)) { browsePath = `/ipfs/${trimmedPath}` + cid = trimmedPath } - onBrowseProp({ path: browsePath }) + onBrowseProp({ path: browsePath, cid }) setPath('') } }, [isValid, trimmedPath, onBrowseProp]) diff --git a/src/files/file-preview/file-thumbnail.tsx b/src/files/file-preview/file-thumbnail.tsx index 33008ed19..fc1d691de 100644 --- a/src/files/file-preview/file-thumbnail.tsx +++ b/src/files/file-preview/file-thumbnail.tsx @@ -1,6 +1,5 @@ import { CID } from 'multiformats/cid' import React, { useState, useEffect, useCallback, type FC } from 'react' -// @ts-expect-error - redux-bundler-react is not typed import { connect } from 'redux-bundler-react' import typeFromExt from '../type-from-ext/index.js' import './file-thumbnail.css' diff --git a/src/files/files-grid/files-grid.tsx b/src/files/files-grid/files-grid.tsx index d70d66392..b01554d6f 100644 --- a/src/files/files-grid/files-grid.tsx +++ b/src/files/files-grid/files-grid.tsx @@ -2,13 +2,12 @@ import React, { useRef, useState, useEffect, useCallback, type FC, type MouseEve import { Trans, withTranslation } from 'react-i18next' import { useDrop } from 'react-dnd' import { NativeTypes } from 'react-dnd-html5-backend' -import { ExtendedFile, FileStream, normalizeFiles } from '../../lib/files.js' +import { normalizeFiles } from '../../lib/files.js' import GridFile from './grid-file.jsx' -// @ts-expect-error - redux-bundler-react is not typed import { connect } from 'redux-bundler-react' import './files-grid.css' import { TFunction } from 'i18next' -import type { ContextMenuFile } from 'src/files/types.js' +import type { ContextMenuFile, ExtendedFile, FileStream } from '../types' import type { CID } from 'multiformats/cid' export interface FilesGridProps { diff --git a/src/files/files-grid/grid-file.tsx b/src/files/files-grid/grid-file.tsx index 3a1f96c03..e2a9bfb01 100644 --- a/src/files/files-grid/grid-file.tsx +++ b/src/files/files-grid/grid-file.tsx @@ -1,11 +1,10 @@ import React, { useRef, useState, useEffect, type FC } from 'react' import { withTranslation } from 'react-i18next' import { useDrag, useDrop, type DropTargetMonitor } from 'react-dnd' -import { FileStream, humanSize, normalizeFiles } from '../../lib/files.js' +import { humanSize, normalizeFiles } from '../../lib/files.js' import { CID } from 'multiformats/cid' import { isBinary } from 'istextorbinary' import FileIcon from '../file-icon/FileIcon.js' -// @ts-expect-error - redux-bundler-react is not typed import { connect } from 'redux-bundler-react' import FileThumbnail from '../file-preview/file-thumbnail.js' import PinIcon from '../pin-icon/PinIcon.js' @@ -15,7 +14,7 @@ import { NativeTypes } from 'react-dnd-html5-backend' import { join, basename } from 'path' import './grid-file.css' import { TFunction } from 'i18next' -import { ContextMenuFile } from '../types.js' +import type { ContextMenuFile, FileStream } from '../types' type SetPinningProps = { cid: CID, pinned: boolean } diff --git a/src/files/modals/bulk-import-modal/bulk-import-modal.tsx b/src/files/modals/bulk-import-modal/bulk-import-modal.tsx index e1e0d21cf..ccb9daacc 100644 --- a/src/files/modals/bulk-import-modal/bulk-import-modal.tsx +++ b/src/files/modals/bulk-import-modal/bulk-import-modal.tsx @@ -1,18 +1,26 @@ import React, { useState, useRef } from 'react' -import Button from '../../../components/button/button.tsx' +import Button from '../../../components/button/button' import { Modal, ModalActions, ModalBody } from '../../../components/modal/modal' import { useTranslation } from 'react-i18next' import * as isIPFS from 'is-ipfs' import Icon from '../../../icons/StrokeDocument.js' import { normalizeFiles } from '../../../lib/files.js' +import { FileStream } from '../../types' -const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props }) => { +interface BulkImportModalProps { + onCancel: () => void + className?: string + onBulkCidImport: (files: FileStream[]) => Promise + [key: string]: any +} + +const BulkImportModal: React.FC = ({ onCancel, className = '', onBulkCidImport, ...props }) => { const [selectedFile, setSelectedFile] = useState(null) const [validationError, setValidationError] = useState(undefined) const bulkCidInputRef = useRef(null) const { t } = useTranslation('files') - const validateFileContents = async (file) => { + const validateFileContents = async (file: File) => { try { const text = await file.text() const lines = text.split('\n').filter(line => line.trim()) @@ -30,8 +38,8 @@ const BulkImportModal = ({ onCancel, className = '', onBulkCidImport, ...props } } } - const onChange = async (event) => { - const file = event.target.files[0] + const onChange: React.ChangeEventHandler = async (event) => { + const file = event.target.files?.[0] if (!file) return const validation = await validateFileContents(file) diff --git a/src/files/pin-icon/PinIcon.js b/src/files/pin-icon/PinIcon.js index 3c0299f37..19655b271 100644 --- a/src/files/pin-icon/PinIcon.js +++ b/src/files/pin-icon/PinIcon.js @@ -5,7 +5,7 @@ import GlyphPinCloud from '../../icons/GlyphPinCloud.js' import '../PendingAnimation.css' /** - * @param {{ t: (key: string) => string, isFailedPin: boolean, isPendingPin: boolean, isRemotePin: boolean, pinned: boolean }} props + * @param {{ t: import('i18next').TFunction, isFailedPin: boolean, isPendingPin: boolean, isRemotePin: boolean, pinned: boolean }} props * @returns {React.ReactElement} */ const PinningIcon = ({ t, isFailedPin, isPendingPin, isRemotePin, pinned }) => { diff --git a/src/files/types.ts b/src/files/types.ts index 34dcaee08..d9cbe3e26 100644 --- a/src/files/types.ts +++ b/src/files/types.ts @@ -8,3 +8,16 @@ export interface ContextMenuFile { path: string pinned: boolean } + +export interface FileStream { + path: string + content: Blob + size: number +} + +export interface FileExt { + filepath?: string + webkitRelativePath?: string +} + +export type ExtendedFile = File & FileExt diff --git a/src/helpers/Portal.js b/src/helpers/Portal.js index 72f56477f..ed75a7cd5 100644 --- a/src/helpers/Portal.js +++ b/src/helpers/Portal.js @@ -1,6 +1,12 @@ import { memo, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' +/** + * @param {Object} props + * @param {string} props.id + * @param {React.ReactNode} props.children + * @param {string|number} [props.zIndex] + */ const Portal = ({ id, children, zIndex }) => { const el = useRef(document.getElementById(id) || document.createElement('div')) const [dynamic] = useState(!el.current.parentElement) @@ -8,7 +14,7 @@ const Portal = ({ id, children, zIndex }) => { useEffect(() => { if (dynamic) { el.current.id = id - zIndex && (el.current.style.zIndex = zIndex) + zIndex && (el.current.style.zIndex = String(zIndex)) document.body.appendChild(el.current) } return () => { diff --git a/src/helpers/connected-context-provider.tsx b/src/helpers/connected-context-provider.tsx index 6da32956f..66de9f84e 100644 --- a/src/helpers/connected-context-provider.tsx +++ b/src/helpers/connected-context-provider.tsx @@ -2,7 +2,6 @@ * @see {@link ./REDUX-BUNDLER-MIGRATION-GUIDE.md} for more information */ import React, { createContext, useContext, useMemo, ReactNode } from 'react' -// @ts-expect-error - redux-bundler-react is not typed import { connect } from 'redux-bundler-react' /** diff --git a/src/helpers/i8n.js b/src/helpers/i8n.js index 4b18b3db0..f39ce3545 100644 --- a/src/helpers/i8n.js +++ b/src/helpers/i8n.js @@ -1,3 +1,6 @@ +/** + * @param {import('i18next').TFunction} translate + */ export const getJoyrideLocales = (translate) => ({ back: translate('tour.back'), close: translate('tour.close'), diff --git a/src/i18n-decorator.js b/src/i18n-decorator.js index 8379e526c..d720cf737 100644 --- a/src/i18n-decorator.js +++ b/src/i18n-decorator.js @@ -2,6 +2,9 @@ import React from 'react' import { I18nextProvider } from 'react-i18next' import i18n from './i18n.js' +/** + * @param {() => React.ReactElement} fn + */ export default function i18nDecorator (fn) { return ( diff --git a/src/i18n.js b/src/i18n.js index e405600ec..ba3febf73 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -16,7 +16,7 @@ i18n .use(ICU) .use(Backend) .use(LanguageDetector) - .init({ + .init(/** @type {import('i18next').InitOptions} */ ({ load: 'currentOnly', // see https://github.com/i18next/i18next-http-backend/issues/61 backend: { backends: [ @@ -29,7 +29,7 @@ i18n expirationTime: (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') ? 1 : 7 * 24 * 60 * 60 * 1000 }, { // HttpBackend - loadPath: (lngs, namespaces) => { + loadPath: (/** @type {string[]} */ lngs, /** @type {string[]} */ namespaces) => { const locale = getValidLocaleCode({ i18n, localeCode: lngs[0], languages: locales }) // ensure a relative path is used to look up the locales, so it works when loaded from /ipfs/ return `locales/${locale}/${namespaces}.json` @@ -56,6 +56,6 @@ i18n bindStore: 'added removed', nsMode: 'default' } - }) + })) export default i18n diff --git a/src/lib/files.js b/src/lib/files.js index b4fabc6f4..4fe6d92a5 100644 --- a/src/lib/files.js +++ b/src/lib/files.js @@ -6,19 +6,13 @@ import filesize from 'filesize' */ /** - * @typedef {Object} FileExt - * @property {string} [filepath] - * @property {string} [webkitRelativePath] + * @typedef {import('../files/types').FileExt} FileExt * - * @typedef {FileExt & File} ExtendedFile + * @typedef {import('../files/types').ExtendedFile} ExtendedFile * - * @typedef {Object} FileStream - * @property {string} path - * @property {Blob} content - * @property {number} size * * @param {ExtendedFile[]} files - * @returns {FileStream[]} + * @returns {import('../files/types').FileStream[]} */ export function normalizeFiles (files) { const streams = [] diff --git a/src/lib/hofs/functions.test.js b/src/lib/hofs/functions.test.js index 56fc54003..29359fb56 100644 --- a/src/lib/hofs/functions.test.js +++ b/src/lib/hofs/functions.test.js @@ -29,10 +29,12 @@ describe('hofFns', function () { }) it('should throw an error if the passed fn is not a function', function () { + // @ts-expect-error - we want to test the error case expect(() => after('not a function', 3)).toThrow(TypeError) }) it('should throw an error if times is not a number', function () { + // @ts-expect-error - we want to test the error case expect(() => after(jest.fn(), 'not a number')).toThrow(TypeError) }) }) @@ -50,6 +52,7 @@ describe('hofFns', function () { }) it('should throw an error if the passed fn is not a function', function () { + // @ts-expect-error - we want to test the error case expect(() => once('not a function')).toThrow(TypeError) }) }) @@ -71,6 +74,7 @@ describe('hofFns', function () { }) it('should throw an error if the passed fn is not a function', function () { + // @ts-expect-error - we want to test the error case expect(() => debounce('not a function')).toThrow(TypeError) }) }) @@ -104,10 +108,12 @@ describe('hofFns', function () { }) it('should throw an error if the passed fn is not a function', function () { + // @ts-expect-error - we want to test the error case expect(() => onlyOnceAfter('not a function', 2)).toThrow(TypeError) }) it('should throw an error if nth is not a number', function () { + // @ts-expect-error - we want to test the error case expect(() => onlyOnceAfter(jest.fn(), 'not a number')).toThrow(TypeError) }) }) diff --git a/src/lib/i18n-localeParser.js b/src/lib/i18n-localeParser.js index 4ff8fce82..4d230d96b 100644 --- a/src/lib/i18n-localeParser.js +++ b/src/lib/i18n-localeParser.js @@ -13,7 +13,7 @@ export default function getValidLocaleCode ({ i18n, localeCode, languages }) { if (info != null) { return localeCode } - + // @ts-expect-error - fallbackLng is not typed correctly. const fallbackLanguages = i18n.options.fallbackLng[localeCode] if (info == null && fallbackLanguages != null) { /** diff --git a/src/lib/tours.js b/src/lib/tours.js index 8f155826e..76c8992f0 100644 --- a/src/lib/tours.js +++ b/src/lib/tours.js @@ -1,5 +1,8 @@ import React from 'react' +/** + * @type {import('react-joyride').CustomTour} + */ export const appTour = { getSteps: ({ t }) => [{ content:
    @@ -22,8 +25,11 @@ export const appTour = { } } +/** + * @type {import('react-joyride').CustomTour} + */ export const welcomeTour = { - getSteps: ({ t, Trans }) => [ + getSteps: ({ t }) => [ { content:

    {t('tour.step1.title')}

    @@ -45,6 +51,9 @@ export const welcomeTour = { } } +/** + * @type {import('react-joyride').CustomTour} + */ export const statusTour = { getSteps: ({ t, Trans }) => [ { @@ -105,6 +114,9 @@ export const statusTour = { } } +/** + * @type {import('react-joyride').CustomTour} + */ export const filesTour = { getSteps: ({ t, Trans }) => [ { @@ -180,6 +192,9 @@ export const filesTour = { } } +/** + * @type {import('react-joyride').CustomTour} + */ export const peersTour = { getSteps: ({ t }) => [ { @@ -219,6 +234,9 @@ export const peersTour = { } } +/** + * @type {import('react-joyride').CustomTour} + */ export const settingsTour = { getSteps: ({ t, Trans }) => [ { diff --git a/src/loader/ComponentLoader.js b/src/loader/ComponentLoader.js index 7d5fd14b4..e7ffc765d 100644 --- a/src/loader/ComponentLoader.js +++ b/src/loader/ComponentLoader.js @@ -1,6 +1,10 @@ import React from 'react' import './ComponentLoader.css' +/** + * @param {Object} props + * @param {React.CSSProperties} [props.style] + */ const ComponentLoader = (props) => (
    diff --git a/src/loader/ComponentLoader.stories.js b/src/loader/ComponentLoader.stories.js index e4d3c40b7..316e5ec76 100644 --- a/src/loader/ComponentLoader.stories.js +++ b/src/loader/ComponentLoader.stories.js @@ -11,10 +11,12 @@ export default { /** * @type {import('@storybook/react').StoryObj} */ -export const Default = () => ( +export const Default = { + render: () => (
    -) + ) +} diff --git a/src/navigation/NavBar.js b/src/navigation/NavBar.js index 9925dd224..ceae85f30 100644 --- a/src/navigation/NavBar.js +++ b/src/navigation/NavBar.js @@ -13,6 +13,14 @@ import StrokeIpld from '../icons/StrokeIpld.js' // Styles import './NavBar.css' +/** + * @param {Object} props + * @param {string} props.to + * @param {React.ComponentType>} props.icon + * @param {string} [props.alternative] + * @param {boolean} [props.disabled] + * @param {string} props.children + */ const NavLink = ({ to, icon, @@ -37,7 +45,7 @@ const NavLink = ({ return ( // eslint-disable-next-line jsx-a11y/anchor-is-valid - +
    @@ -50,6 +58,10 @@ const NavLink = ({ ) } +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + */ export const NavBar = ({ t }) => { const codeUrl = 'https://github.com/ipfs/ipfs-webui' const bugsUrl = `${codeUrl}/issues` diff --git a/src/navigation/NavBar.stories.js b/src/navigation/NavBar.stories.js index 41eacb1eb..d61042394 100644 --- a/src/navigation/NavBar.stories.js +++ b/src/navigation/NavBar.stories.js @@ -1,7 +1,6 @@ import React from 'react' -import { action } from '@storybook/addon-actions' -import { withKnobs, boolean } from '@storybook/addon-knobs' +import { withKnobs } from '@storybook/addon-knobs' import i18n from '../i18n.js' import { NavBar } from './NavBar.js' @@ -16,14 +15,12 @@ export default { /** * @type {import('@storybook/react').StoryObj} */ -export const Default = () => ( +export const Default = { + render: () => (
    -) + ) +} diff --git a/src/pins/PinsPage.js b/src/pins/PinsPage.js index 2c1fc9d10..f8cf0a9a3 100644 --- a/src/pins/PinsPage.js +++ b/src/pins/PinsPage.js @@ -4,6 +4,15 @@ import { connect } from 'redux-bundler-react' import { withTranslation } from 'react-i18next' import PinsStatuses from './PinsStatuses.js' +/** + * @param {Object} props + * @param {string[]} props.pendingPins + * @param {string[]} props.failedPins + * @param {string[]} props.completedPins + * @param {(pin: string) => void} props.doDismissCompletedPin + * @param {(pin: string) => void} props.doDismissFailedPin + * @param {(pin: string) => void} props.doCancelPendingPin + */ const PinsPage = ({ pendingPins, failedPins, completedPins, doDismissCompletedPin, doDismissFailedPin, doCancelPendingPin }) => { return (
    diff --git a/src/pins/PinsStatuses.js b/src/pins/PinsStatuses.js index 8fd897705..4452736cc 100644 --- a/src/pins/PinsStatuses.js +++ b/src/pins/PinsStatuses.js @@ -10,6 +10,12 @@ const Status = { Failed: 2 } +/** + * @param {import('i18next').TFunction} t + * @param {string} pin + * @param {number} status + * @param {(pin: string) => void} onDismiss + */ const Pin = (t, pin, status = Status.Pending, onDismiss) => { const [service, cid] = pin.split(':') @@ -29,9 +35,11 @@ const Pin = (t, pin, status = Status.Pending, onDismiss) => { } return (
  • + {/* @ts-expect-error - alt & title are not valid props for GlyphPinCloud */} {cid} | {service} + {/* @ts-expect-error - alt is not valid prop for span */}
  • ) } +/** + * @param {Object} props + * @param {string[]} props.pendingPins + * @param {string[]} props.failedPins + * @param {string[]} props.completedPins + * @param {(pin: string) => void} props.onDismissFailedPin + * @param {(pin: string) => void} props.onDismissCompletedPin + * @param {(pin: string) => void} props.doCancelPendingPin + * @param {import('i18next').TFunction} props.t + */ const PinsStatuses = ({ pendingPins, failedPins, completedPins, onDismissFailedPin, onDismissCompletedPin, doCancelPendingPin, t }) => { return (
    diff --git a/src/pins/PinsStatuses.stories.js b/src/pins/PinsStatuses.stories.js index 646c81771..7801c43fc 100644 --- a/src/pins/PinsStatuses.stories.js +++ b/src/pins/PinsStatuses.stories.js @@ -6,41 +6,38 @@ import PinsStatusesComponent from './PinsStatuses.js' /** * @type {import('@storybook/react').StoryObj} */ -const PinStatusesStory = () => ( -
    - -
    -) +export const PinStatusesStory = { + render: () => ( +
    + +
    + ) +} /** * @type {import('@storybook/react').Meta} */ export default { title: 'Pins/PinsStatuses', - component: PinStatusesStory, + component: PinsStatusesComponent, decorators: [i18nDecorator] } - -/** - * @type {import('@storybook/react').StoryObj} - */ -export const PinsStatuses = {} diff --git a/src/welcome/WelcomePage.js b/src/welcome/WelcomePage.js index 9266683b9..2f83cd599 100644 --- a/src/welcome/WelcomePage.js +++ b/src/welcome/WelcomePage.js @@ -1,7 +1,7 @@ import React from 'react' import { Helmet } from 'react-helmet' import { connect } from 'redux-bundler-react' -import { withTranslation } from 'react-i18next' +import { withTranslation, Trans } from 'react-i18next' import ReactJoyride from 'react-joyride' import withTour from '../components/tour/withTour.js' import { welcomeTour } from '../lib/tours.js' @@ -14,6 +14,16 @@ import AboutIpfs from '../components/about-ipfs/AboutIpfs.js' import AboutWebUI from '../components/about-webui/AboutWebUI.js' import ComponentLoader from '../loader/ComponentLoader.js' +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + * @param {string} props.apiUrl + * @param {boolean} props.ipfsInitFailed + * @param {boolean} props.ipfsConnected + * @param {boolean} props.ipfsReady + * @param {boolean} props.toursEnabled + * @param {(data: any) => void} props.handleJoyrideCallback + */ const WelcomePage = ({ t, apiUrl, ipfsInitFailed, ipfsConnected, ipfsReady, toursEnabled, handleJoyrideCallback }) => { if (!ipfsInitFailed && !ipfsReady) { return @@ -31,7 +41,7 @@ const WelcomePage = ({ t, apiUrl, ipfsInitFailed, ipfsConnected, ipfsReady, tour
    { +/** + * @param {Object} props + * @param {import('i18next').TFunction} props.t + * @param {boolean} props.connected + * @param {boolean} props.sameOrigin + * + * @returns {JSX.Element} + */ +const ConnectionStatus = ({ t: _t, connected, sameOrigin: _sameOrigin }) => { if (connected) { return (
    @@ -57,6 +75,7 @@ const ConnectionStatus = ({ t, connected, sameOrigin }) => { ) } return ( + // @ts-expect-error - IsNotConnected needs type fixes ) } diff --git a/tsconfig.json b/tsconfig.json index c6b45c1ab..ab8da7f60 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -38,11 +38,9 @@ "declarationMap": true, "incremental": true, "composite": true, - "types": [ - "node" - ], "typeRoots": [ - "@types" + "./node_modules/@types", + "./@types" ], "allowSyntheticDefaultImports": true, "isolatedModules": true, @@ -53,14 +51,11 @@ "*": ["*", "*.ts", "*.tsx"] } }, - "exclude": [ - "./src/test", - "src/**/*.test.js" - ], "include": [ "**/*.ts", - // "**/*.tsx", - "@types", + "**/*.tsx", + "**/*.json", + "@types/**/*", // "src/**/*.js", // TODO: Include all js files when typecheck passes "src/bundles/files/**/*.js", "src/bundles/analytics.js", @@ -80,19 +75,30 @@ "src/env.js", "src/lib/hofs/**/*.js", "src/lib/guards.js", - "src/files/file-preview/file-thumbnail.tsx", "src/files/type-from-ext/index.js", - "src/files/files-grid/files-grid.tsx", - "src/files/files-grid/grid-file.tsx", "src/files/file-icon/FileIcon.js", "src/files/pin-icon/PinIcon.js", - "src/icons/**/*.tsx", "src/components/checkbox/Checkbox.js", "src/files/type-from-ext/extToType.js", - "src/helpers/context-bridge.tsx", - "src/helpers/connected-context-provider.tsx", - "src/components/version-link/**/*", - "src/lib/*.ts", - "src/components/modal/**/*" + "src/pins/**/*", + "src/blank/**/*", + "src/contexts/**/*", + "src/constants/**/*", + "src/helpers/**/*", + "src/icons/**/*", + "src/loader/**/*", + "src/navigation/**/*", + "src/welcome/**/*", + "src/components/tour/withTour.js", + "src/lib/tours.js", + "src/components/is-connected/IsConnected.js", + "src/components/is-not-connected/IsNotConnected.js", + "src/components/about-ipfs/AboutIpfs.js", + "src/components/about-webui/AboutWebUI.js", + "src/components/box/Box.js", + "src/components/shell/Shell.js", + "src/i18n-decorator.js", + "src/i18n.js", + "src/lib/i18n-localeParser.js" ] }