import type { EntityID, Thread } from '@wovin/core' import type { Accessor, Component } from 'solid-js' import type { FakeBlock } from '../components/BlockTree' import { createDebouncedMemo } from '@solid-primitives/memo' import { makePersisted } from '@solid-primitives/storage' import { useParams, useSearchParams } from '@solidjs/router' import AddIcon from '@suid/icons-material/Add' import Fab from '@suid/material/Fab' import { lastWriteWins, observableArrayMap, queryAndMap, querySingleAndMap, withoutDeleted } from '@wovin/core' import { observable } from '@wovin/core/mobx' import { Logger } from 'besonders-logger' import { createEffect, createMemo, createResource, createSignal, Match, Show, Suspense, Switch } from 'solid-js' import { storageError } from '../appInit' import { ApplogView } from '../components/ApplogView' import { BlockTree } from '../components/BlockTree' import { Breadcrumbs } from '../components/Breadcrumbs' import { createDeferredResource, Iconify, ResourceSpinner, Spinner } from '../components/mini-components' import { useFilters } from '../components/PillFilter' import { useAgent } from '../data/agent/AgentState' import { DefaultAgentBanner, getSubOrShare, StorageErrorBanner } from '../data/agent/utils-agent' import { blockThreadWithRecursiveKids } from '../data/block-utils-nowin' import { ENTITY_DEF } from '../data/data-types' import { createKidInFocusOrRoot, useGlobalInputHandlers } from '../data/keybindings' import { useMatchTree } from '../data/match-tree' import { plaintextStringToTiptap } from '../data/note3-utils-nodeps' import { useSearchContext } from '../data/search' import { parseSmartQuery } from '../data/smart-list' import { retrievePubDataWithExtras } from '../ipfs/store-sync' import { useAppSettings } from '../ui/app-settings' import { LazyRender } from '../ui/lazy-render' import { PreviewInfoPanel } from '../ui/preview' import { DBContext, useBlocksMatchingSearch, useCurrentThread, useFocus, useRawThread, useRootsOfMaybeNested, withDS } from '../ui/reactive' import { devMode, onClickOrLongPress, replaceSearchParamsInUrl, useLocationNavigate, useSingleUrlParam } from '../ui/utils-ui' const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.DEBUG) // eslint-disable-line unused-imports/no-unused-vars export const [skipUserSetup, setSkipUserSetup] = makePersisted( createSignal(false), { name: 'note3.skipUserSetup' }, ) let renderCount = 0 export const MainPage: Component<{ searchOpen: Accessor }> = (props) => { ;(renderCount++ > 0 ? WARN : DEBUG)('!!! MAINPAGE RENDER !!!', renderCount) const { locnav, location, navigate } = useLocationNavigate() const agent = useAgent() const appThread = useRawThread() const params = useParams() const focus = useFocus() const [search] = useSearchContext() const [urlParams, _setUrlParams] = useSearchParams() const activeFilters = useFilters() const appSettings = createMemo(() => useAppSettings()) // If the settingsID changes (in the future), the whole 'thing' is replaced // const isZenview = createMemo(() => location.pathname.startsWith('/zenview')) const isZenview = createMemo(() => urlParams.zen) useGlobalInputHandlers() const [pubFromUrl] = useSingleUrlParam('pub') const [previewFromUrl] = useSingleUrlParam('preview') const selectedPubOrPreview = createMemo(() => pubFromUrl() || previewFromUrl()) const matchingSubOrShare = createMemo(() => selectedPubOrPreview() ? getSubOrShare(selectedPubOrPreview()) : null) // ? const searchStr = createMemo(() => urlParams.search) // if (pubFromUrl()) { // ? pull in BG when there is a pub // defer(async () => { // const pubCID = await resolveIPNS(pubFromUrl()) // pullCarToEdge(pubCID) // }) // } // PREVIEW // const previewID = createMemo(() => { if (DEBUG.isEnabled) DEBUG(`[previewID] memo`, { previewFromUrl: previewFromUrl(), pubFromUrl: pubFromUrl() }) if (previewFromUrl()) return previewFromUrl() if (pubFromUrl() && !matchingSubOrShare()) return pubFromUrl() return null }) const [previewData, { refetch: refetchPreview }] = useSubResource(() => previewID()) const [displayThread] = createResource(() => { if (DEBUG.isEnabled) DEBUG(`[currentDS] resource source:`, { previewData, previewOrMissingPub: previewID() }) return { previewID: previewID(), previewData: (!!previewID()) && previewData.state === 'ready' && previewData(), // focussed: params.blockID, } }, async ({ previewID, previewData }): Promise => { // explicit type to no be strict about / expect it being e.g. ThreadWithoutFilters on the type if (DEBUG.isEnabled) DEBUG(`[currentDS] resource load`, { previewID, previewData, appThread }) // await sleep(1000) // HACK: defer thread render to display spinner while loading if (previewID) { // if (!previewData) throw ERROR(`Invalid previewData state`, previewData) if (!previewData) return null return previewData.thread } // ? quick test shows this made things worse - but it *could* be an improvement in some situations? - probably not... eager pre-calc is mostly a bad idea // if (focussed) { // return withDS(appThread, () => useBlk(focussed).threadWithRecursiveKids) // } return appThread }) if (DEBUG.isEnabled) createEffect(() => DEBUG(`currentDS state:`, displayThread.state)) createEffect(() => { if (displayThread.latest && !!previewID() != displayThread.latest.readOnly) { ERROR(`!! READONLY ASSUMPTION DID NOT HOLD - tell manu!`, { pomp: previewID(), displayDS: displayThread.latest }) } }) // // const blocksMatchingSearch = createMemo(() => rawDS() && withDS(rawDS(), () => useBlocksMatchingSearch(search()))) // // const blocksMatchingSearchAndParents = createMemo(() => rawDS() && withDS(rawDS(), () => useBlocksMatchingSearch(search()))) const debouncedSearch = createDebouncedMemo(() => search(), 500) const [rootsResource] = createDeferredResource(() => ({ displayThread: displayThread(), focussed: params.blockID, search: debouncedSearch(), searchOpen: props.searchOpen(), activeFilters: activeFilters(), urlPath: location.pathname, // urlParams: { ...urlParams }, previewOrMissingPub: previewID(), }), async ({ displayThread, search, searchOpen, activeFilters, focussed, urlPath, previewOrMissingPub }) => { DEBUG(`[roots] finding roots of`, { displayThread, search, searchOpen, activeFilters, focussed, urlPath, // urlParams, previewOrMissingPub, }) if (!displayThread) { return null } if (searchOpen) { // SEARCH if (focussed) { displayThread = blockThreadWithRecursiveKids(displayThread, focussed) // HACK?! } if (activeFilters.includes('by-tag')) { // doesSearchContainAnyTag(search)) { return { complete: observable.box(true), // HACK roots: [{ fake: { type: 'smartlist', // WARN: this could work, but actually the whole resource will re-calc on search change get content() { return plaintextStringToTiptap(search) }, get smartQuery() { return parseSmartQuery(search) }, } satisfies FakeBlock, }], } } if (!search || search.length <= 2) return null // HACK: don't bother with short searches const blocksMatchingSearch = withDS(displayThread, () => useBlocksMatchingSearch(search)) if (DEBUG.isEnabled) DEBUG(`[roots] blocksMatchingSearch`, blocksMatchingSearch) if (!blocksMatchingSearch.length) return null const matchingSearchButNotKids = withDS(displayThread, () => useRootsOfMaybeNested(blocksMatchingSearch)) if (DEBUG.isEnabled) DEBUG(`[roots] matchingSearchButNotKids`, matchingSearchButNotKids) return { complete: observable.box(true), roots: observableArrayMap(() => matchingSearchButNotKids.map(blockID => ({ blockID })), { name: `searchRoots<${displayThread.nameAndSizeUntracked}>`, }), } } else if (focussed) { return { complete: observable.box(true), roots: [{ blockID: focussed as EntityID }] } } else if (previewOrMissingPub || urlPath === '/timeline') { // TIMELINE const lastWriteThread = lastWriteWins(displayThread) /* HACK no situation handling */ const currentThread = withoutDeleted(lastWriteThread) const matchData = useMatchTree(currentThread, { order: 'date_desc', }) // HACK: to filter out deleted roots const filteredRoots = observableArrayMap(() => matchData.roots.filter(({ blockID }) => { return !queryAndMap(lastWriteThread, { en: blockID, at: ENTITY_DEF.isDeleted }, 'vl')[0] }), ) return { ...matchData, roots: filteredRoots } // return observableArrayMap(() => // withDS(displayDS, () => { // return useRoots().map(blockID => ({ blockID })) // // const rootRels = useKidRelationIDs(homeBlock()) // // const homeBlocks = rootRels.map(rel => useRel(rel).block) // // LOG(`Home block kids:`, { rootRels, homeBlocks }) // // return homeBlocks.map(blockID => ({ blockID })) // }), { name: `previewRoots<${displayDS.nameAndSizeUntracked}>` }) // } } return { complete: observable.box(true), roots: [{ placeholderMode: true, fake: {} }], } }) createEffect(() => DEBUG(`roots:`, rootsResource(), displayThread())) const addRootNode = (newRoot: boolean) => { WARN(`addRootNode`, event) // TODO: double tap / hold equivalent of ctrlKey on mobile createKidInFocusOrRoot(newRoot ? null : focus(), withDS(displayThread(), useCurrentThread), { location, navigate }) } return (
{ /*
urlP: {previewFromUrl()}
}>
DS: {currentDS()}
*/ }
{ WARN(`Long press: `, e, long, onClickOrLongPress) e.preventDefault() addRootNode(long || e.ctrlKey) }} >
{/*
{JSON.stringify(roots(), undefined, 2)}
*/} }>
Failed to load thread
{previewData.error.message ?? JSON.stringify(previewData.error, undefined, 4)}
Loading preview for thread: {' '} {previewID()}
{/* HACK: suspense necessary? */} }> No currentDS}> { /* - HACK: Sidebar query needs index */ } {/*
{JSON.stringify({f:focus(),r:roots()})}
*/}
!isZenview() && !!(params.blockID && params.blockID !== appSettings().homeBlock /* && roots.get().length > 1 */)} />
)} > { /* Results: {roots()?.length} */ } [...rootsResource().roots]} fallback={
No roots - reload?
} reset={[focus(), search()]} allLoaded={() => rootsResource().complete.get()} loadMore={rootsResource().loadMore} > {(item, isInitial) => (
{/*
{JSON.stringify(id)}
*/}
)}
{querySingleAndMap(previewData().info.logs, { at: 'pub/name' }, 'vl').get()} Exit Zen view
) } export function useSubResource(subID: Accessor) { const appThread = useRawThread() const [previewData, { refetch }] = createResource(subID, async (previewOrMissingPub) => { try { if (DEBUG.isEnabled) DEBUG(`[useSubResource] for`, previewOrMissingPub) // await new Promise(resolve => setTimeout(resolve, 700)) // ; throw new Error(`Eddi`) if (!previewOrMissingPub) return null return await retrievePubDataWithExtras(appThread, previewOrMissingPub) } catch (err) { ERROR(`Failed to fetch preview`, previewOrMissingPub, err) throw new Error(`Failed to fetch preview: ${err.message}`) } }) DEBUG.isEnabled && createEffect(() => { DEBUG(`[useSubResource]`, { data: previewData(), state: previewData.state, err: previewData.error }) }) return [previewData, { refetch }] as const }