import type { EntityID } from '@wovin/core/applog' import type { Accessor, Component } from 'solid-js' import type { BlockPanelDef } from '../data/block-panels' import type { SmartList } from '../data/smart-list' import type { Block } from '../data/VMs/BlockVM' import type { TiptapContent } from '../data/VMs/TypeMap' import { holdTillFirstWrite } from '@wovin/core' import { Logger } from 'besonders-logger' import { defaults } from 'lodash-es' import { createContext, createMemo, createSignal, For, Suspense, untrack } from 'solid-js' import { useBlockContext } from '../data/block-ui-helpers' import { BlockVM } from '../data/VMs/BlockVM' import { DBContext, useReadOnlyState, useThreadFromContext } from '../ui/reactive' import { focusBlockAsInput } from '../ui/utils-ui' import { Iconify, Spinner } from './mini-components' import { draggingBlock, SortableBlocks } from './SortableBlocks' import { TreeItem } from './TreeItem' const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line unused-imports/no-unused-vars export const MAX_DEPTH = 12 // TODO: properly handle max depth UX // Default autocollapse depths (can be overridden by app settings) export const AUTOCOLLAPSE_DEPTH_DEFAULT = 2 export const AUTOCOLLAPSE_DEPTH_FOCUSSED_DEFAULT = 2 // children of focus and their children export const AUTOCOLLAPSE_DEPTH_TIMELINE_DEFAULT = 1 // only immediate children in timeline export const AUTOCOLLAPSE_DEPTH_TIMELINE_SMARTLIST_DEFAULT = 0 // smartlists fully collapsed in timeline export interface BlockContextType { // ! context values are not reactive per se - use signals/resources/obvervables if reactivity is needed id: EntityID depth: number relationToParent: EntityID parentContext: BlockContextType | null hiddenTags?: Accessor smartList?: SmartList autoExpandDepth?: Accessor setAutoExpandDepth: (depth: number) => void setPanel: (panel: BlockPanelDef) => void } export type FakeBlock = Partial> // HACK: smartQuery actual field? export const BlockContext = createContext(null) export const defaultAutoCompleteCallback = (chosenString: string) => DEBUG('default', { chosenString }) export const [autoCompleteOptions, setAutoCompleteOptions] = createSignal([]) export const [autoCompleteCallback, setAutoCompleteCallback] = createSignal(defaultAutoCompleteCallback) export const BlockTree: Component<{ blockID?: EntityID fake?: FakeBlock placeholderMode?: boolean }> = (props) => { DEBUG(` created`, untrack(() => ({ ...props }))) const readOnly = useReadOnlyState() // const focus = useFocus() // const location = useLocation() // const navigate = useNavigate() const placeholderBuilt = createMemo(() => { if (!props.placeholderMode) return null if (!props.fake) throw ERROR(` Invalid: placeholderMode but no fake`, props) const builder = BlockVM.buildNew( defaults(props.fake, { content: '' as any as TiptapContent, // content needs to be set for the block to be considered existent //HACK: Allow empty string as content? }), ) const applogs = builder.build() return { blockID: builder.en, applogs } }) const thread = createMemo(() => { let thread = useThreadFromContext() if (props.placeholderMode) { thread = holdTillFirstWrite(thread, placeholderBuilt().applogs, { onFirstWrite: (_logs) => { LOG(`Persisting placeholder - zoom to:`, placeholderBuilt().blockID) // zoomToBlock({ id: placeholderBuilt().blockID, focus: placeholderBuilt().blockID }, location, navigate) // ! looses input focus as it re-renders the page history.pushState({ block: placeholderBuilt().blockID }, '', `#/block/${placeholderBuilt().blockID}`) // HACK: workaround, but breaks solid-router partially (that's why we pointed the home routes to a redirection) return undefined // insert _logs unmodified }, }) focusBlockAsInput({ id: placeholderBuilt().blockID }) } return thread }) const blockID = createMemo(() => props.blockID ?? placeholderBuilt()?.blockID) const items = createMemo(function treeItems() { DEBUG(`.items()`, { blockID: blockID(), fake: props.fake }) if (blockID()) { return [{ id: `FakeDragRelation_${blockID()}`, blockID: blockID() }] } // or - we have a special root if (props.fake?.type === 'smartlist') { return [{ id: `FakeDragRelation_SmartList<${props.fake.smartQuery.source}>`, fake: props.fake }] // const query = parseSmartQuery(props.fake.smartQuery) // if (!query) return [] // HACK: we shouldn't get here // // const smartLists = observableArrayMap(() => getSmartLists(query)) // // const items = smartLists.map((smartList) => ({ id: `FakeDragRelation_SmartList::${props.search}::${smartList.tag}`, smartList })) // const items = smartLists.map((smartList) => ({ // id: `FakeDragRelation_SmartList::${props.search}::${smartList.tag}`, // query: smartQuery, // })) // DEBUG(` smartlist:`, { search: props.search, smartLists, items }) // return items } throw ERROR(`Invalid SortableBlocks props`, { props }) }) return ( }> thread()}> {(item) => { DEBUG(` :`, item) return }} ) } export const AutoCompleteMenu: Component<{ options: string[] callback?: (chosenString: string) => void }> = (props) => { const cb = (chosenString) => { DEBUG({ chosenString }) if (props.callback && typeof props.callback === 'function') props.callback(chosenString) } return ( {(item, index) => cb(item)} data-index={index()} size='small'>{item}} ) } export const LinkOpenMenu: Component<{ link: Accessor | null> }> = (props) => { const blockContext = useBlockContext() return ( Open ) }