Files
AFFiNE-Mirror/blocksuite/affine/gfx/note/src/toolbar/note-menu.ts
T
2025-03-22 14:39:04 +00:00

211 lines
6.3 KiB
TypeScript

import { addAttachments } from '@blocksuite/affine-block-attachment';
import { insertLinkByQuickSearchCommand } from '@blocksuite/affine-block-bookmark';
import { addImages, LoadedImageIcon } from '@blocksuite/affine-block-image';
import { AttachmentIcon, LinkIcon } from '@blocksuite/affine-components/icons';
import { MAX_IMAGE_WIDTH } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import type { NoteChildrenFlavour } from '@blocksuite/affine-shared/types';
import {
getImageFilesFromLocal,
openFileOrFiles,
} from '@blocksuite/affine-shared/utils';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
import { effect } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import type { NoteToolOption } from '../note-tool.js';
import { NOTE_MENU_ITEMS } from './note-menu-config.js';
export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
static override styles = css`
:host {
position: absolute;
display: flex;
z-index: -1;
}
.menu-content {
display: flex;
align-items: center;
justify-content: center;
}
.button-group-container {
display: flex;
justify-content: center;
align-items: center;
gap: 14px;
fill: var(--affine-icon-color);
}
.button-group-container svg {
width: 20px;
height: 20px;
}
.divider {
width: 1px;
height: 24px;
background: var(--affine-border-color);
transform: scaleX(0.5);
margin: 0 14px;
}
`;
override type: GfxToolsFullOptionValue['type'] = 'affine:note';
private async _addImages() {
this._imageLoading = true;
const imageFiles = await getImageFilesFromLocal();
const ids = await addImages(this.edgeless.std, imageFiles, {
maxWidth: MAX_IMAGE_WIDTH,
});
this._imageLoading = false;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.selection.set({ elements: ids });
}
private _onHandleLinkButtonClick() {
const [_, { insertedLinkType }] = this.edgeless.service.std.command.exec(
insertLinkByQuickSearchCommand
);
insertedLinkType
?.then(type => {
const flavour = type?.flavour;
if (!flavour) return;
this.edgeless.std
.getOptional(TelemetryProvider)
?.track('CanvasElementAdded', {
control: 'toolbar:general',
page: 'whiteboard editor',
module: 'toolbar',
type: flavour.split(':')[1],
});
})
.catch(console.error);
}
override disconnectedCallback() {
super.disconnectedCallback();
}
override firstUpdated() {
this.disposables.add(
effect(() => {
const tool = this.gfx.tool.currentToolOption$.value;
if (tool?.type !== 'affine:note') return;
this.childFlavour = tool.childFlavour;
this.childType = tool.childType;
this.tip = tool.tip;
})
);
}
override render() {
const { childType } = this;
return html`
<edgeless-slide-menu>
<div class="menu-content">
<!-- add to edgeless -->
<div class="button-group-container">
<edgeless-tool-icon-button
.activeMode=${'background'}
.tooltip=${'Image'}
@click=${this._addImages}
.disabled=${this._imageLoading}
>
${LoadedImageIcon}
</edgeless-tool-icon-button>
<edgeless-tool-icon-button
.activeMode=${'background'}
.tooltip=${html`<affine-tooltip-content-with-shortcut
data-tip="${'Link'}"
data-shortcut="${'@'}"
></affine-tooltip-content-with-shortcut>`}
@click=${() => {
this._onHandleLinkButtonClick();
}}
>
${LinkIcon}
</edgeless-tool-icon-button>
<edgeless-tool-icon-button
.activeMode=${'background'}
.tooltip=${'File'}
@click=${async () => {
const file = await openFileOrFiles();
if (!file) return;
await addAttachments(this.edgeless.std, [file]);
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.edgeless.std
.getOptional(TelemetryProvider)
?.track('CanvasElementAdded', {
control: 'toolbar:general',
page: 'whiteboard editor',
module: 'toolbar',
segment: 'toolbar',
type: 'attachment',
});
}}
>
${AttachmentIcon}
</edgeless-tool-icon-button>
</div>
<div class="divider"></div>
<!-- add to note -->
<div class="button-group-container">
${repeat(
NOTE_MENU_ITEMS,
item => item.childFlavour,
item => html`
<edgeless-tool-icon-button
.active=${childType === item.childType}
.activeMode=${'background'}
.tooltip=${item.tooltip}
@click=${() =>
this.onChange({
childFlavour: item.childFlavour,
childType: item.childType,
tip: item.tooltip,
})}
>
${item.icon}
</edgeless-tool-icon-button>
`
)}
</div>
</div>
</edgeless-slide-menu>
`;
}
@state()
private accessor _imageLoading = false;
@property({ attribute: false })
accessor childFlavour!: NoteChildrenFlavour;
@property({ attribute: false })
accessor childType!: string | null;
@property({ attribute: false })
accessor onChange!: (
props: Partial<{
childFlavour: NoteToolOption['childFlavour'];
childType: string | null;
tip: string;
}>
) => void;
@property({ attribute: false })
accessor tip!: string;
}