import { useEffect, useRef, useMemo, useImperativeHandle } from 'preact/hooks' import { RefObject } from 'preact' import { forwardRef } from 'preact/compat' import { Model } from '../model' import { ViewModel } from '../viewModel' import { useContributions, useContributionAPI } from '../contribute' import { toPX } from '../utils/common' import { Theme } from '../interface/theme' import { IntlLanguage } from '../interface/intl' import { LayoutTopic, TopicData } from '../interface/topic' import { Contribution, ContributionAPI } from '../interface/contribute' import { ViewType } from '../constant' import { debug } from '../utils/debug' import { doLayout } from '../layout' import { LayoutType } from '../interface/layout' import { useTextEditor } from '../utils/useTextEditor' import { Links } from './Links' import Topic from './Topic' import styles from './index.module.css' interface MindmapProps { theme: Theme locale: IntlLanguage value: TopicData onChange: (value: TopicData) => void contributions: Contribution[] layout: LayoutType } const Mindmap = forwardRef( (props: MindmapProps, ref: RefObject<ContributionAPI>) => { const { onChange, contributions, layout, theme } = props const model = Model.useContainer() const viewModel = ViewModel.useContainer() const editorRef = useRef<HTMLDivElement>(null) const { root } = model const textEditor = useTextEditor() const contributionAPI = useContributionAPI({ view: editorRef, layout, textEditor, }) const { slots } = useContributions(contributionAPI, contributions) const mindmapSlots = slots.filter( (slot) => slot?.viewType === ViewType.mindmap, ) const { mindmap: mindmapTheme } = theme const { layoutRoot, canvasWidth, canvasHeight } = useMemo(() => { return doLayout(root, { layout, theme }) }, [root, layout, theme]) debug('layoutRoot', layoutRoot) useEffect(() => { onChange?.(root) }, [root]) useEffect(() => { viewModel.setLayoutRoot(layoutRoot) }, [layoutRoot]) useImperativeHandle(ref, () => contributionAPI, [contributionAPI]) useEffect(() => { try { editorRef.current.scrollIntoView({ block: 'center', inline: 'center' }) } catch {} }, []) return ( <div className={styles.container}> <div className={styles.scrollContainer} style={{ background: mindmapTheme.background }} > <div ref={editorRef} data-type={ViewType.mindmap} className={styles.editor} style={{ width: toPX(canvasWidth), height: toPX(canvasHeight), }} draggable > <svg className={styles.svgCanvas} width={canvasWidth} height={canvasHeight} xmlns="http://www.w3.org/2000/svg" // to make generated svg has background color style={{ background: mindmapTheme.background }} > <Links layoutRoot={layoutRoot} layout={layout} /> <Topics layoutRoot={layoutRoot} /> </svg> {textEditor.editor} {mindmapSlots} </div> </div> </div> ) }, ) function Topics({ layoutRoot }: { layoutRoot: LayoutTopic }) { return ( <g className="topics"> {layoutRoot.descendants().map((node) => { return <Topic key={node.data.id} node={node} /> })} </g> ) } export { Mindmap } export type { MindmapProps }