Files
AFFiNE-Mirror/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-mindmap-button.ts
T
2024-12-20 16:48:10 +00:00

297 lines
7.6 KiB
TypeScript

/* 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`
<div
role="button"
class="style-item ${value === this.mindmapStyle ? 'active' : ''}"
@click=${() => this.onSelect(value)}
>
${icon}
</div>
`
);
}
@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`
<editor-icon-button
aria-label=${name}
.tooltip=${name}
.tipPosition=${'top'}
.active=${this.mindmapLayout === value}
.activeMode=${'background'}
@click=${() => this.onSelect(value)}
>
${icon}
</editor-icon-button>
`
);
}
@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<MindmapElementModel>();
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`
<editor-menu-button
.contentPadding=${'8px'}
.button=${html`
<editor-icon-button aria-label="Style" .tooltip=${'Style'}>
${MindmapStyleIcon}${SmallArrowDownIcon}
</editor-icon-button>
`}
>
<edgeless-change-mindmap-style-panel
.mindmapStyle=${this._getCommonStyle()}
.onSelect=${this._updateStyle}
>
</edgeless-change-mindmap-style-panel>
</editor-menu-button>
`,
this._isSubnode()
? nothing
: html`
<editor-menu-button
.button=${html`
<editor-icon-button aria-label="Layout" .tooltip=${'Layout'}>
${this.layout.icon}${SmallArrowDownIcon}
</editor-icon-button>
`}
>
<edgeless-change-mindmap-layout-panel
.mindmapLayout=${this.layout.value}
.onSelect=${this._updateLayoutType}
>
</edgeless-change-mindmap-layout-panel>
</editor-menu-button>
`,
].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`
<edgeless-change-mindmap-button
.elements=${mindmaps}
.nodes=${elements.filter(e => e.type === 'shape')}
.edgeless=${edgeless}
>
</edgeless-change-mindmap-button>
`;
}