import type { Component, ParentComponent } from 'solid-js' /* eslint-disable ts/no-use-before-define */ import { createLazyMemo } from '@solid-primitives/memo' import { MetaProvider, Title } from '@solidjs/meta' import { HashRouter, Navigate, Route, useIsRouting, useLocation, useSearchParams } from '@solidjs/router' import { Logger } from 'besonders-logger' import classNames from 'classnames' import capitalize from 'lodash-es/capitalize' import { createEffect, createMemo, createResource, createSignal, lazy, on, onCleanup, Show, Suspense, untrack } from 'solid-js' import { appInit, initialized, setInitialized } from './appInit' import { BackLink } from './components/BackLink' import { ExtraSearchResults, HeaderSearchBar } from './components/bars/SearchBar' import { TopBar } from './components/bars/TopBar' import { BlockSettingsDialog } from './components/BlockSettings' import { ShareChooser } from './components/BulletMenu' import { RedirectWorkaround, Spinner } from './components/mini-components' import { PwaReloadPrompt } from './components/PwaReloadPrompt' import { getApplogDB, initApplogDB } from './data/ApplogDB' import { SearchContext, useExtraSearchResults } from './data/search' import { BlockVM } from './data/VMs/BlockVM' import { useVM } from './data/VMs/MappedVMbase' import { AgentSetupPage, shouldShowAgentSetup } from './pages/AgentSetupPage' import { Help } from './pages/HelpPage' import { InspectPage } from './pages/InspectPage' import { useAppSettings } from './ui/app-settings' import { DBContext, withDS } from './ui/reactive' import { createDebouncedWithEqCheck, replaceUriParts, useSingleUrlParam } from './ui/utils-ui' import './data/devtools-helpers' const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO, { prefix: '[App]' }) // eslint-disable-line unused-imports/no-unused-vars const MAX_BLOCK_TITLE_LENGTH = 20 // import MainPage from './MainPage' // import SettingsPage from './SettingsPage' // import TestPage from './TestPage' const MainPage = lazy(async () => ({ default: (await import('./pages/MainPage')).MainPage })) const SettingsPage = lazy(() => import('./pages/SettingsPage')) const [searchOpen, setSearchOpen] = createSignal(false) // TODO: refactor to search provider export const [blockSettingsID, setBlockSettingsID] = createSignal('') export function showBlockSettings(blkID) { setBlockSettingsID(blkID) } export function hideBlockSettings() { setBlockSettingsID('') } export const AppRoot: Component = () => { // const isPreview = createMemo(() => (window.location.hash.includes('preview=') || window.location.hash.includes('search='))) // HACK const homeBlockUrl = createLazyMemo(() => { // lazy so the errors are not thrown DEBUG('calc homeBlockUrl', initialized()) // only if url is explicitly #/home if (!initialized()) return null const appSettings = withDS(getApplogDB(), () => useAppSettings()) if (!appSettings.homeBlock) throw ERROR(`missing appsetting: homeBlockUrl`, appSettings) if (!appSettings.homeBlock) { WARN(`missing appsetting: homeBlockUrl`, appSettings) return '/timeline' } return `/block/${appSettings.homeBlock}` }) return ( ( )} /> { const location = useLocation() const [searchParams] = useSearchParams() const needsAgentSetup = createMemo(() => !searchParams.preview && !searchParams.zen && shouldShowAgentSetup()) return ( } > ) }} /> } /> } /> ( 404 - page not found )} /> ) } const StartPageRedirect: Component = () => { const location = useLocation() const [searchParams] = useSearchParams() const isPreviewOrSearch = createMemo(() => !!searchParams.preview || !!searchParams.search) // const isPreview = createMemo(() => (window.location.hash.includes('preview=') || window.location.hash.includes('search='))) // HACK const redirectUrl = createMemo(() => { if (!initialized()) return null const appSettings = withDS(getApplogDB(), () => useAppSettings()) const startPage: string = appSettings.startPage let newPathname // if (isPreviewOrSearch) newPathname = '/timeline' // else { DEBUG({ startPage, appSettings }) if (startPage === 'home-block') { newPathname = '/home' } else if (startPage === 'empty-block') { newPathname = '/new' } else if (startPage === 'timeline') { newPathname = '/timeline' } else { WARN(`unknown startPage (using it either way): `, startPage) newPathname = startPage } // } return replaceUriParts(location, { pathname: newPathname }) // to keep preview= & co }) return ( <> {!isPreviewOrSearch() ? ( {/* Sadly broken... */} ) : ( }> )} > ) } let renderCount = 0 export const App: ParentComponent = (props) => { ;(renderCount++ > 0 ? WARN : DEBUG)('!!! APP RENDER !!!', renderCount) // trace(true) const [searchParams] = useSearchParams() const pubFromUrl = createMemo(() => searchParams.pub) const location = useLocation() // const navigate = useNavigate() const isRouting = useIsRouting() const isPreview = createMemo(() => !!searchParams.preview) const isZenview = createMemo(() => !!searchParams.zen) // const isZenview = createMemo(() => location.pathname.startsWith('/zenview')) const [searchInUrl, setSearchInUrl] = useSingleUrlParam('search') const [search, setSearch] = createSignal(untrack(() => searchInUrl())) ////////// // INIT // ////////// const cleanup = new Set<() => void>() const [applogDB] = createResource(async () => { LOG('[App] Initializing DB') const db = await initApplogDB() LOG('[App] DB init done', db) const cleanUp = await appInit({ pubFromUrl: untrack(() => pubFromUrl()) }) cleanup.add(cleanUp) LOG('[App] cleanup ready', cleanup) setInitialized(true) return db }) onCleanup(() => { for (const eachCleanup of cleanup) { eachCleanup() } }) const setSearchInUrlDebounced = createDebouncedWithEqCheck( searchInUrl, // will only update if still the same after debounce (newSearch: string) => { DEBUG(`debounced setSearchInUrl:`, newSearch) setSearchInUrl( newSearch, // { replace: !!searchParams.search == !!newSearch } // we debounce already, so... new entry ) }, 1400, // ? after which time of having a search entry should it have it's own separate history entry? ) createEffect(on(() => searchInUrl(), (searchInUrl) => { DEBUG(`searchInUrl changed to`, searchInUrl, { searchInState: search() }) if (searchInUrl !== search()) { // changed from browser navigation setSearch(searchInUrl || null) setSearchOpen(!!searchInUrl) } })) createEffect(on(() => search(), (search) => { DEBUG(`search changed to`, search) // if (!search) setSearchOpen(false) if (search !== null && search !== undefined) { setSearchInUrlDebounced(search) } })) createEffect(on(() => searchOpen(), (searchOpen) => { DEBUG(`searchOpen changed to`, searchOpen, { search: search() }) if (!searchOpen) { setSearch(null) setSearchInUrl(null) setSearchInUrlDebounced.cancel() } })) const searchResults = createMemo(() => { if (!applogDB()) return null const results = withDS(applogDB(), () => useExtraSearchResults({ search$: search }))() DEBUG(`.searchResults`, results) return results }) const title = createMemo(() => { const location = useLocation() const pathname = createMemo(() => location.pathname.slice(1)) DEBUG({ pathname: pathname() }) if (searchOpen()) return `Search '${search()}'` if (pathname().startsWith('block')) { const blockIDmemo = createMemo(() => pathname().split('block/')[1]) const blockContent = initialized() ? useVM(BlockVM, blockIDmemo, applogDB())().contentPlaintext ?? 'empty block' : '' const trimmedContent = blockContent.length > MAX_BLOCK_TITLE_LENGTH ? `${blockContent.substring(0, MAX_BLOCK_TITLE_LENGTH)}…` : blockContent return trimmedContent } if (pathname()) return `${capitalize(pathname())}` return null }) const updateSchoelaceDarkmode = (enabled: boolean) => document.documentElement.classList.toggle('sl-theme-dark', enabled) const prefersDarkQuery = window.matchMedia('(prefers-color-scheme: dark)') updateSchoelaceDarkmode(window.matchMedia && prefersDarkQuery.matches) prefersDarkQuery.addEventListener('change', (event) => { LOG(`Dark mode`, event.matches) updateSchoelaceDarkmode(event.matches) }) LOG('App rendering', location.pathname) return ( {title() ? `${title()} - ` : ''}Note3 {/* */} {/* */} {/* */} {/* (i) workaround for https://github.com/solidjs/solid-router/discussions/296 */} { /* stuck? window.location.reload()}>reload */ } { /* setSearchOpen(false)} /> */ } {/* innit? {JSON.stringify(initialized())} */} )} > setSearchOpen(false)} /> )} > {props.children /* Routes */} ) }
innit? {JSON.stringify(initialized())}