/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { MindmapBalanceLayoutIcon, MindmapLeftLayoutIcon, MindmapRightLayoutIcon, MindmapStyleFour, MindmapStyleIcon, MindmapStyleOne, MindmapStyleThree, MindmapStyleTwo, SmallArrowDownIcon, } from '@blocksuite/affine-components/icons'; import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar'; import type { MindmapElementModel, ShapeElementModel, } from '@blocksuite/affine-model'; import { LayoutType, MindmapStyle } from '@blocksuite/affine-model'; import { EditPropsStore } from '@blocksuite/affine-shared/services'; import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; import { css, html, LitElement, nothing, type TemplateResult } from 'lit'; import { property, state } from 'lit/decorators.js'; import { join } from 'lit/directives/join.js'; import { repeat } from 'lit/directives/repeat.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; const MINDMAP_STYLE_LIST = [ { value: MindmapStyle.ONE, icon: MindmapStyleOne, }, { value: MindmapStyle.FOUR, icon: MindmapStyleFour, }, { value: MindmapStyle.THREE, icon: MindmapStyleThree, }, { value: MindmapStyle.TWO, icon: MindmapStyleTwo, }, ]; interface LayoutItem { name: string; value: LayoutType; icon: TemplateResult<1>; } const MINDMAP_LAYOUT_LIST: LayoutItem[] = [ { name: 'Left', value: LayoutType.LEFT, icon: MindmapLeftLayoutIcon, }, { name: 'Radial', value: LayoutType.BALANCE, icon: MindmapBalanceLayoutIcon, }, { name: 'Right', value: LayoutType.RIGHT, icon: MindmapRightLayoutIcon, }, ] as const; export class EdgelessChangeMindmapStylePanel extends LitElement { static override styles = css` :host { display: flex; align-items: center; justify-content: center; flex-direction: row; gap: 8px; background: var(--affine-background-overlay-panel-color); } .style-item { border-radius: 4px; } .style-item > svg { vertical-align: middle; } .style-item.active, .style-item:hover { cursor: pointer; background-color: var(--affine-hover-color); } `; override render() { return repeat( MINDMAP_STYLE_LIST, item => item.value, ({ value, icon }) => html`
this.onSelect(value)} > ${icon}
` ); } @property({ attribute: false }) accessor mindmapStyle!: MindmapStyle | null; @property({ attribute: false }) accessor onSelect!: (style: MindmapStyle) => void; } export class EdgelessChangeMindmapLayoutPanel extends LitElement { static override styles = css` :host { display: flex; align-items: center; justify-content: center; flex-direction: row; gap: 8px; } `; override render() { return repeat( MINDMAP_LAYOUT_LIST, item => item.value, ({ name, value, icon }) => html` this.onSelect(value)} > ${icon} ` ); } @property({ attribute: false }) accessor mindmapLayout!: LayoutType | null; @property({ attribute: false }) accessor onSelect!: (style: LayoutType) => void; } export class EdgelessChangeMindmapButton extends WithDisposable(LitElement) { private readonly _updateLayoutType = (layoutType: LayoutType) => { this.edgeless.std.get(EditPropsStore).recordLastProps('mindmap', { layoutType, }); this.elements.forEach(element => { element.layoutType = layoutType; element.layout(); }); this.layoutType = layoutType; }; private readonly _updateStyle = (style: MindmapStyle) => { this.edgeless.std.get(EditPropsStore).recordLastProps('mindmap', { style }); this._mindmaps.forEach(element => (element.style = style)); }; private get _mindmaps() { const mindmaps = new Set(); return this.elements.reduce((_, el) => { mindmaps.add(el); return mindmaps; }, mindmaps); } get layout() { const layoutType = this.layoutType ?? this._getCommonLayoutType(); return MINDMAP_LAYOUT_LIST.find(item => item.value === layoutType)!; } private _getCommonLayoutType() { const values = countBy(this.elements, element => element.layoutType); const max = maxBy(Object.entries(values), ([_k, count]) => count); return max ? (Number(max[0]) as LayoutType) : LayoutType.BALANCE; } private _getCommonStyle() { const values = countBy(this.elements, element => element.style); const max = maxBy(Object.entries(values), ([_k, count]) => count); return max ? (Number(max[0]) as MindmapStyle) : MindmapStyle.ONE; } private _isSubnode() { return ( this.nodes.length === 1 && (this.nodes[0].group as MindmapElementModel).tree.element !== this.nodes[0] ); } override render() { return join( [ html` ${MindmapStyleIcon}${SmallArrowDownIcon} `} > `, this._isSubnode() ? nothing : html` ${this.layout.icon}${SmallArrowDownIcon} `} > `, ].filter(button => button !== nothing), renderToolbarSeparator ); } @property({ attribute: false }) accessor edgeless!: EdgelessRootBlockComponent; @property({ attribute: false }) accessor elements!: MindmapElementModel[]; @state() accessor layoutType!: LayoutType; @property({ attribute: false }) accessor nodes!: ShapeElementModel[]; } export function renderMindmapButton( edgeless: EdgelessRootBlockComponent, elements?: (ShapeElementModel | MindmapElementModel)[] ) { if (!elements?.length) return nothing; const mindmaps: MindmapElementModel[] = []; elements.forEach(e => { if (e.type === 'mindmap') { mindmaps.push(e as MindmapElementModel); } const group = edgeless.service.surface.getGroup(e.id); if (group && 'type' in group && group.type === 'mindmap') { mindmaps.push(group as MindmapElementModel); } }); if (mindmaps.length === 0) { return nothing; } return html` e.type === 'shape')} .edgeless=${edgeless} > `; }