From 6c07b8dcf07efc6ed8b9ea79f4e7dd0f8d934c69 Mon Sep 17 00:00:00 2001 From: "Jules Sam. Randolph" Date: Sat, 21 Nov 2020 19:29:53 -0300 Subject: [PATCH] feat: support for loading HTML from URI --- packages/render-html/package.json | 2 +- packages/render-html/src/LoadHTML.tsx | 93 +++++++++++++++++++ packages/render-html/src/RenderHTML.tsx | 20 ++-- packages/render-html/src/RenderHTMLDebug.tsx | 21 ++++- packages/render-html/src/TBlockRenderer.tsx | 2 +- .../render-html/src/TDocumentRenderer.tsx | 8 ++ packages/render-html/src/defaultRenderers.tsx | 1 + packages/render-html/src/index.ts | 3 + packages/render-html/src/shared-types.ts | 8 +- yarn.lock | 20 ++-- 10 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 packages/render-html/src/LoadHTML.tsx create mode 100644 packages/render-html/src/TDocumentRenderer.tsx diff --git a/packages/render-html/package.json b/packages/render-html/package.json index 6079737ed..28cdf1b42 100644 --- a/packages/render-html/package.json +++ b/packages/render-html/package.json @@ -72,6 +72,6 @@ ] }, "dependencies": { - "@native-html/transient-render-engine": "^2.0.0" + "@native-html/transient-render-engine": "^2.0.1" } } diff --git a/packages/render-html/src/LoadHTML.tsx b/packages/render-html/src/LoadHTML.tsx new file mode 100644 index 000000000..811636e88 --- /dev/null +++ b/packages/render-html/src/LoadHTML.tsx @@ -0,0 +1,93 @@ +import React, { ReactElement, useEffect, useState } from 'react'; +import { ActivityIndicator, Text, View } from 'react-native'; +import { RenderHTMLProps } from './shared-types'; + +interface HTMLLoaderProps extends RenderHTMLProps { + children: (resolvedHTML: string) => ReactElement; +} + +interface LoaderInternalState { + loading: boolean; + error: boolean; + resolvedHTML: string | null; +} + +const ERROR_STATE = { + error: true, + loading: false, + resolvedHTML: null +}; + +async function loadHTMLResource(uri: string): Promise { + const response = await fetch(uri); + if (response.ok) { + const html = await response.text(); + return { + resolvedHTML: html, + error: false, + loading: false + }; + } + return ERROR_STATE; +} + +function useLoader(props: HTMLLoaderProps) { + const { uri, html } = props; + const [loadState, setState] = useState({ + error: false, + loading: false, + resolvedHTML: html || null + }); + const { error } = loadState; + useEffect(() => { + let cancelled = false; + if (!error && uri) { + setState({ loading: true, error: false, resolvedHTML: null }); + loadHTMLResource(uri) + .then((state) => { + !cancelled && setState(state); + }) + .catch(() => { + !cancelled && setState(ERROR_STATE); + }); + } + return () => { + cancelled = true; + }; + }, [error, uri]); + return loadState; +} + +function defaultRenderError(props: RenderHTMLProps) { + return ( + + + Failed to load {props.uri} + + + ); +} + +function defaultRenderLoading() { + return ( + + + + ); +} +export default function LoadHTML(props: HTMLLoaderProps): ReactElement | null { + const { remoteErrorView, remoteLoadingView, children } = props; + const { resolvedHTML, error, loading } = useLoader(props); + if (error) { + return remoteErrorView!.call(null, props); + } + if (loading) { + return remoteLoadingView!.call(null, props); + } + return children?.call(null, resolvedHTML!) || null; +} + +LoadHTML.defaultProps = { + remoteErrorView: defaultRenderError, + remoteLoadingView: defaultRenderLoading +}; diff --git a/packages/render-html/src/RenderHTML.tsx b/packages/render-html/src/RenderHTML.tsx index 5c55fd6cf..e225ba246 100644 --- a/packages/render-html/src/RenderHTML.tsx +++ b/packages/render-html/src/RenderHTML.tsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { Platform } from 'react-native'; import { RenderHTMLProps } from './shared-types'; @@ -10,6 +10,7 @@ import SharedPropsContext, { import TNodeRenderersContext from './context/TNodeRenderersContext'; import TChildrenRenderer from './TChildrenRenderer'; import RenderHTMLDebug from './RenderHTMLDebug'; +import LoadHTML from './LoadHTML'; export type RenderHTMLPropTypes = Record; @@ -138,18 +139,25 @@ const defaultProps: { debug: __DEV__ }; -export default function RenderHTML(props: RenderHTMLProps) { +function RenderResolvedHTML(props: RenderHTMLProps) { const ttree = useTTree(props); - const Wrapper = props.debug ? RenderHTMLDebug : Fragment; + return ; +} + +export default function RenderHTML(props: RenderHTMLProps) { return ( - + - + + {(resolvedHTML) => ( + + )} + - + ); } diff --git a/packages/render-html/src/RenderHTMLDebug.tsx b/packages/render-html/src/RenderHTMLDebug.tsx index 16176cb64..2d88f0f7e 100644 --- a/packages/render-html/src/RenderHTMLDebug.tsx +++ b/packages/render-html/src/RenderHTMLDebug.tsx @@ -1,9 +1,13 @@ -import React from 'react'; +import React, { Fragment } from 'react'; +import { PropsWithChildren } from 'react'; import { useEffect } from 'react'; -import RenderHTML from './RenderHTML'; import { RenderHTMLProps } from './shared-types'; -export default function RenderHTMLDebug(props: RenderHTMLProps) { +function RenderHTMLProd(props: PropsWithChildren) { + return {props.children}; +} + +function RenderHTMLDev(props: PropsWithChildren) { useEffect(() => { if (typeof props.contentWidth !== 'number') { console.warn( @@ -16,5 +20,14 @@ export default function RenderHTMLDebug(props: RenderHTMLProps) { ); } }, [props.contentWidth]); - return React.createElement(RenderHTML, props); + return {props.children}; +} + +export default function RenderHTMLDebug( + props: PropsWithChildren +) { + if (props.debug && __DEV__) { + return React.createElement(RenderHTMLDev, props); + } + return React.createElement(RenderHTMLProd, props); } diff --git a/packages/render-html/src/TBlockRenderer.tsx b/packages/render-html/src/TBlockRenderer.tsx index 9ad15c5e8..b319fa099 100644 --- a/packages/render-html/src/TBlockRenderer.tsx +++ b/packages/render-html/src/TBlockRenderer.tsx @@ -24,7 +24,7 @@ function mergeCollapsedMargins( return [nativeStyle, additionalStyles]; } -const TDefaultBlockRenderer: TDefaultRenderer = ({ +export const TDefaultBlockRenderer: TDefaultRenderer = ({ tnode, key, children: overridingChildren, diff --git a/packages/render-html/src/TDocumentRenderer.tsx b/packages/render-html/src/TDocumentRenderer.tsx new file mode 100644 index 000000000..9e8c57e75 --- /dev/null +++ b/packages/render-html/src/TDocumentRenderer.tsx @@ -0,0 +1,8 @@ +import { TBlock } from '@native-html/transient-render-engine'; +import { TNodeGenericRendererProps } from './shared-types'; + +const TDocumentRenderer = ({}: TNodeGenericRendererProps) => { + return null; +}; + +export default TDocumentRenderer; diff --git a/packages/render-html/src/defaultRenderers.tsx b/packages/render-html/src/defaultRenderers.tsx index 4c183babf..278e3f10e 100644 --- a/packages/render-html/src/defaultRenderers.tsx +++ b/packages/render-html/src/defaultRenderers.tsx @@ -1,4 +1,5 @@ import type { TBlock } from '@native-html/transient-render-engine'; +import React from 'react'; import ImgRenderer from './renderers/ImgRenderer'; import ListRenderer from './renderers/ListRenderer'; import type { RendererProps } from './shared-types'; diff --git a/packages/render-html/src/index.ts b/packages/render-html/src/index.ts index c3b68c335..b0dbbf3dd 100644 --- a/packages/render-html/src/index.ts +++ b/packages/render-html/src/index.ts @@ -1,3 +1,6 @@ import RenderHTML from './RenderHTML'; export * from './shared-types'; export default RenderHTML; + +export { default as useTRenderEngine } from './useTRenderEngine'; +export { default as useTTree } from './useTTree'; diff --git a/packages/render-html/src/shared-types.ts b/packages/render-html/src/shared-types.ts index f5ed46909..57990ade5 100644 --- a/packages/render-html/src/shared-types.ts +++ b/packages/render-html/src/shared-types.ts @@ -12,7 +12,7 @@ import type { TNode, TBlock } from '@native-html/transient-render-engine'; -import { ReactNode } from 'react'; +import { ReactElement, ReactNode } from 'react'; import { CSSPropertyNameList, MixedStyleDeclaration @@ -204,15 +204,15 @@ export interface RenderHTMLProps

/** * Replace the default wrapper with a function that takes your content as the first parameter. */ - customWrapper?: (innerNodes: ReactNode) => ReactNode; + customWrapper?: (innerNodes: ReactNode) => ReactElement; /** * Replace the default loader while fetching a remote website's content. */ - remoteLoadingView?: (props: RenderHTMLProps

, state: any) => ReactNode; + remoteLoadingView?: (props: RenderHTMLProps

) => ReactElement; /** * Replace the default error if a remote website's content could not be fetched. */ - remoteErrorView?: (props: RenderHTMLProps

, state: any) => ReactNode; + remoteErrorView?: (props: RenderHTMLProps

) => ReactElement; /** * The default value in pixels for 1em */ diff --git a/yarn.lock b/yarn.lock index 3b261db98..bfc347499 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2910,28 +2910,28 @@ __metadata: languageName: node linkType: hard -"@native-html/css-processor@npm:1.5.0": - version: 1.5.0 - resolution: "@native-html/css-processor@npm:1.5.0" +"@native-html/css-processor@npm:1.5.1": + version: 1.5.1 + resolution: "@native-html/css-processor@npm:1.5.1" dependencies: css-to-react-native: ^3.0.0 peerDependencies: "@types/react-native": "*" csstype: ^3.0.4 - checksum: 7263eb07cd677080bd2b7958d8eb304858df3c0aeba7a415537dea0e01d31dd64fad068f96071cfd131a240a96bb9c66e11a3b0bda2bbdbfb2b80d7a5785e3d0 + checksum: 6c4e253314ff5e438964b1d6621f40cb41ab02cee2aa4ccfaf2f2e8adeeaa1576fc0469ceea297c40569ec6d496fff53f7037bb2c35f593d615feef78dcc5a20 languageName: node linkType: hard -"@native-html/transient-render-engine@npm:^2.0.0": - version: 2.0.0 - resolution: "@native-html/transient-render-engine@npm:2.0.0" +"@native-html/transient-render-engine@npm:^2.0.1": + version: 2.0.1 + resolution: "@native-html/transient-render-engine@npm:2.0.1" dependencies: - "@native-html/css-processor": 1.5.0 + "@native-html/css-processor": 1.5.1 htmlparser2: ^5.0.1 peerDependencies: "@types/react-native": "*" react-native: ^0.63.0 - checksum: 6d7d520f91b8b19685676026931cda0852a46262585fa706005f4db01c5454814d4a319eaecaf5650991076587e9e1dfbcad619993716532a1920d5359124c6b + checksum: 8bee3f1c032972ebbc1d108745c5052e9fc35e5c39087a9d64a29db5c324cc3cc148089a841ce41cf02d8ca9f61b0619a962053af7788ec5a9b2426ec26fc060 languageName: node linkType: hard @@ -14216,7 +14216,7 @@ fsevents@^1.2.7: "@babel/preset-react": ^7.12.7 "@babel/preset-typescript": ^7.12.1 "@babel/runtime": ^7.12.5 - "@native-html/transient-render-engine": ^2.0.0 + "@native-html/transient-render-engine": ^2.0.1 "@react-native-community/bob": ^0.16.2 "@release-it/conventional-changelog": ^2.0.0 "@types/jest": ^26.0.14