diff --git a/src/pages/Importer/Index.tsx b/src/pages/Importer/Index.tsx index 00bae2d..3c595eb 100644 --- a/src/pages/Importer/Index.tsx +++ b/src/pages/Importer/Index.tsx @@ -224,6 +224,7 @@ export const Importer: FC> { + navigation.navigate('MainMenu', { fromWikiID: createdWikiWorkspace.id }); navigation.navigate('WikiWebView', { id: createdWikiWorkspace.id }); }} > diff --git a/src/pages/WikiWebView/useTiddlyWiki.ts b/src/pages/WikiWebView/useTiddlyWiki.ts index ab4c7f2..d3742d9 100644 --- a/src/pages/WikiWebView/useTiddlyWiki.ts +++ b/src/pages/WikiWebView/useTiddlyWiki.ts @@ -1,14 +1,16 @@ +/* eslint-disable @typescript-eslint/promise-function-async */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ /* eslint-disable unicorn/no-null */ import { Asset } from 'expo-asset'; import * as fs from 'expo-file-system'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import expoFileSystemSyncadaptorUiAssetID from '../../../assets/plugins/syncadaptor-ui.html'; import expoFileSystemSyncadaptorAssetID from '../../../assets/plugins/syncadaptor.html'; import { getWikiFilePath, getWikiTiddlerStorePath } from '../../constants/paths'; import { replaceTiddlerStoreScriptToTriggerFullReload } from '../../services/WikiHookService'; import { getSkinnyTiddlersJSONFromSQLite } from '../../services/WikiStorageService'; import { IWikiWorkspace } from '../../store/workspace'; +import { usePromiseValue } from '../../utils/usePromiseValue'; export interface IHtmlContent { html: string; @@ -17,30 +19,29 @@ export interface IHtmlContent { } export function useTiddlyWiki(workspace: IWikiWorkspace, injectHtmlAndTiddlersStore: (htmlContent: IHtmlContent) => void, webviewLoaded: boolean, keyToTriggerReload: number) { const [loadHtmlError, setLoadHtmlError] = useState(''); - + /** + * @url file:///data/user/0/host.exp.exponent/files/wikis/wiki/index.html or 'file:///data/user/0/host.exp.exponent/cache/ExponentAsset-8568a405f924c561e7d18846ddc10c97.html' + */ + const html = usePromiseValue(() => fs.readAsStringAsync(getWikiFilePath(workspace))); + /** + * @url file:///data/user/0/host.exp.exponent/files/wikis/wiki/tiddlerStore.json + */ + const tiddlerStoreScript = usePromiseValue(() => fs.readAsStringAsync(getWikiTiddlerStorePath(workspace))); + const skinnyTiddlerStore = usePromiseValue(() => getSkinnyTiddlersJSONFromSQLite(workspace)); + const pluginJSONStrings = usePromiseValue(() => getTidGiMobilePlugins()); useEffect(() => { - if (!webviewLoaded) return; - const fetchHTML = async () => { - try { - const [html, tiddlerStoreScript, skinnyTiddlerStore, pluginJSONStrings] = await Promise.all([ - fs.readAsStringAsync(getWikiFilePath(workspace)), // file:///data/user/0/host.exp.exponent/files/wikis/wiki/index.html or 'file:///data/user/0/host.exp.exponent/cache/ExponentAsset-8568a405f924c561e7d18846ddc10c97.html' - fs.readAsStringAsync(getWikiTiddlerStorePath(workspace)), // file:///data/user/0/host.exp.exponent/files/wikis/wiki/tiddlerStore.json - getSkinnyTiddlersJSONFromSQLite(workspace), - getTidGiMobilePlugins(), - ]); - - const htmlWithPrefix = `${html}`; - // inject tidgi syncadaptor plugins - const tidgiMobilePlugins = `,${pluginJSONStrings.expoFileSystemSyncadaptor},${pluginJSONStrings.expoFileSystemSyncadaptorUi}`; - const tiddlerStoreScriptWithTidGiMobilePlugins = `${patchTiddlyWiki(tiddlerStoreScript).slice(0, -1)}${tidgiMobilePlugins}]`; - injectHtmlAndTiddlersStore({ html: htmlWithPrefix, tiddlerStoreScript: tiddlerStoreScriptWithTidGiMobilePlugins, skinnyTiddlerStore }); - } catch (error) { - console.error(error, (error as Error).stack); - setLoadHtmlError((error as Error).message); - } - }; - void fetchHTML(); - }, [workspace, injectHtmlAndTiddlersStore, webviewLoaded, keyToTriggerReload]); + if (!webviewLoaded || !html || !tiddlerStoreScript || !skinnyTiddlerStore || !pluginJSONStrings) return; + try { + const htmlWithPrefix = `${html}`; + // inject tidgi syncadaptor plugins + const tidgiMobilePlugins = `,${pluginJSONStrings.expoFileSystemSyncadaptor},${pluginJSONStrings.expoFileSystemSyncadaptorUi}`; + const tiddlerStoreScriptWithTidGiMobilePlugins = `${patchTiddlyWiki(tiddlerStoreScript).slice(0, -1)}${tidgiMobilePlugins}]`; + injectHtmlAndTiddlersStore({ html: htmlWithPrefix, tiddlerStoreScript: tiddlerStoreScriptWithTidGiMobilePlugins, skinnyTiddlerStore }); + } catch (error) { + console.error(error, (error as Error).stack); + setLoadHtmlError((error as Error).message); + } + }, [injectHtmlAndTiddlersStore, webviewLoaded, keyToTriggerReload, html, tiddlerStoreScript, skinnyTiddlerStore, pluginJSONStrings]); return { loadHtmlError }; } function patchTiddlyWiki(tiddlyWikiHTML: string): string { diff --git a/src/utils/usePromiseValue.ts b/src/utils/usePromiseValue.ts new file mode 100644 index 0000000..78b1e4e --- /dev/null +++ b/src/utils/usePromiseValue.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { AsyncReturnType } from 'type-fest'; + +/** + * Use value from service, especially constant value that never changes + * This will only update once, won't listen on later update. + * @param valuePromise A promise contain the value we want to use in React + * @param defaultValue empty array or undefined, as initial value + */ +export function usePromiseValue( + asyncValue: () => Promise, + defaultValue?: AsyncReturnType, + dependency: unknown[] = [], +): T | DefaultValueType { + const [value, valueSetter] = useState(defaultValue as T | DefaultValueType); + // use initial value + useEffect(() => { + void (async () => { + try { + valueSetter(await asyncValue()); + } catch (error) { + console.error(error); + if (defaultValue !== undefined) { + valueSetter(defaultValue); + } + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, dependency); + + return value; +}