Files
AFFiNE-Mirror/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-frame-button.ts
T
2024-12-20 15:38:06 +08:00

258 lines
7.5 KiB
TypeScript

import {
NoteIcon,
RenameIcon,
UngroupButtonIcon,
} from '@blocksuite/affine-components/icons';
import { toast } from '@blocksuite/affine-components/toast';
import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import {
type ColorScheme,
DEFAULT_NOTE_HEIGHT,
FRAME_BACKGROUND_COLORS,
type FrameBlockModel,
NoteDisplayMode,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { GfxExtensionIdentifier } from '@blocksuite/block-std/gfx';
import {
countBy,
deserializeXYWH,
maxBy,
serializeXYWH,
WithDisposable,
} from '@blocksuite/global/utils';
import { html, LitElement, nothing } from 'lit';
import { property, query } from 'lit/decorators.js';
import { join } from 'lit/directives/join.js';
import { when } from 'lit/directives/when.js';
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
import type { PickColorEvent } from '../../edgeless/components/color-picker/types.js';
import {
packColor,
packColorsWithColorScheme,
} from '../../edgeless/components/color-picker/utils.js';
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
import type { EdgelessFrameManager } from '../../edgeless/frame-manager.js';
import { mountFrameTitleEditor } from '../../edgeless/utils/text.js';
function getMostCommonColor(
elements: FrameBlockModel[],
colorScheme: ColorScheme
): string | null {
const colors = countBy(elements, (ele: FrameBlockModel) => {
return typeof ele.background === 'object'
? (ele.background[colorScheme] ?? ele.background.normal ?? null)
: ele.background;
});
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : null;
}
export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') {
this.frames.forEach(ele =>
this.service.updateElement(
ele.id,
packColor('background', { ...event.detail })
)
);
return;
}
this.frames.forEach(ele =>
ele[event.type === 'start' ? 'stash' : 'pop']('background')
);
};
get service() {
return this.edgeless.service;
}
private _insertIntoPage() {
if (!this.edgeless.doc.root) return;
const rootModel = this.edgeless.doc.root;
const notes = rootModel.children.filter(
model =>
matchFlavours(model, ['affine:note']) &&
model.displayMode !== NoteDisplayMode.EdgelessOnly
);
const lastNote = notes[notes.length - 1];
const referenceFrame = this.frames[0];
let targetParent = lastNote?.id;
if (!lastNote) {
const targetXYWH = deserializeXYWH(referenceFrame.xywh);
targetXYWH[1] = targetXYWH[1] + targetXYWH[3];
targetXYWH[3] = DEFAULT_NOTE_HEIGHT;
const newAddedNote = this.edgeless.doc.addBlock(
'affine:note',
{
xywh: serializeXYWH(...targetXYWH),
},
rootModel.id
);
targetParent = newAddedNote;
}
this.edgeless.doc.addBlock(
'affine:surface-ref',
{
reference: this.frames[0].id,
refFlavour: 'affine:frame',
},
targetParent
);
toast(this.edgeless.host, 'Frame has been inserted into doc');
}
private _setFrameBackground(color: string) {
this.frames.forEach(frame => {
this.service.updateElement(frame.id, { background: color });
});
}
protected override render() {
const { frames } = this;
const len = frames.length;
const onlyOne = len === 1;
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
const background =
getMostCommonColor(frames, colorScheme) ?? '--affine-palette-transparent';
return join(
[
onlyOne
? html`
<editor-icon-button
aria-label=${'Insert into Page'}
.tooltip=${'Insert into Page'}
.iconSize=${'20px'}
.labelHeight=${'20px'}
@click=${this._insertIntoPage}
>
${NoteIcon}
<span class="label">Insert into Page</span>
</editor-icon-button>
`
: nothing,
onlyOne
? html`
<editor-icon-button
aria-label="Rename"
.tooltip=${'Rename'}
.iconSize=${'20px'}
@click=${() =>
mountFrameTitleEditor(this.frames[0], this.edgeless)}
>
${RenameIcon}
</editor-icon-button>
`
: nothing,
html`
<editor-icon-button
aria-label="Ungroup"
.tooltip=${'Ungroup'}
.iconSize=${'20px'}
@click=${() => {
this.edgeless.doc.captureSync();
const frameMgr = this.edgeless.std.get(
GfxExtensionIdentifier('frame-manager')
) as EdgelessFrameManager;
frames.forEach(frame =>
frameMgr.removeAllChildrenFromFrame(frame)
);
frames.forEach(frame => {
this.edgeless.service.removeElement(frame);
});
this.edgeless.service.selection.clear();
}}
>
${UngroupButtonIcon}
</editor-icon-button>
`,
when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'),
() => {
const { type, colors } = packColorsWithColorScheme(
colorScheme,
background,
this.frames[0].background
);
return html`
<edgeless-color-picker-button
class="background"
.label=${'Background'}
.pick=${this.pickColor}
.color=${background}
.colors=${colors}
.colorType=${type}
.palettes=${FRAME_BACKGROUND_COLORS}
>
</edgeless-color-picker-button>
`;
},
() => html`
<editor-menu-button
.contentPadding=${'8px'}
.button=${html`
<editor-icon-button
aria-label="Background"
.tooltip=${'Background'}
>
<edgeless-color-button
.color=${background}
></edgeless-color-button>
</editor-icon-button>
`}
>
<edgeless-color-panel
.value=${background}
.options=${FRAME_BACKGROUND_COLORS}
@select=${(e: ColorEvent) => this._setFrameBackground(e.detail)}
>
</edgeless-color-panel>
</editor-menu-button>
`
),
].filter(button => button !== nothing),
renderToolbarSeparator
);
}
@query('edgeless-color-picker-button.background')
accessor backgroundButton!: EdgelessColorPickerButton;
@property({ attribute: false })
accessor edgeless!: EdgelessRootBlockComponent;
@property({ attribute: false })
accessor frames: FrameBlockModel[] = [];
}
export function renderFrameButton(
edgeless: EdgelessRootBlockComponent,
frames?: FrameBlockModel[]
) {
if (!frames?.length) return nothing;
return html`
<edgeless-change-frame-button
.edgeless=${edgeless}
.frames=${frames}
></edgeless-change-frame-button>
`;
}