,
- >(key: K, value: ConnectorElementProps[K]) {
- this.doc.captureSync();
- this.elements
- .filter(notEqual(key, value))
- .forEach(element =>
- this.crud.updateElement(element.id, { [key]: value })
- );
- }
-
- private _setConnectorRough(rough: boolean) {
- this._setConnectorProp('rough', rough);
- }
-
- private _setConnectorStrokeStyle(strokeStyle: StrokeStyle) {
- this._setConnectorProp('strokeStyle', strokeStyle);
- }
-
- private _setConnectorStrokeWidth(strokeWidth: number) {
- this._setConnectorProp('strokeWidth', strokeWidth);
- }
-
- private _showAddButtonOrTextMenu() {
- if (this.elements.length === 1 && !this.elements[0].text) {
- return 'button';
- }
- if (!this.elements.some(e => !e.text)) {
- return 'menu';
- }
- return 'nothing';
- }
-
- override render() {
- const colorScheme = this.edgeless.surface.renderer.getColorScheme();
- const elements = this.elements;
- const selectedColor = getMostCommonColor(elements, colorScheme);
- const selectedMode = getMostCommonMode(elements);
- const selectedLineSize = getMostCommonLineWidth(elements);
- const selectedRough = getMostCommonRough(elements);
- const selectedLineStyle = getMostCommonLineStyle(elements);
- const selectedStartPointStyle = getMostCommonEndpointStyle(
- elements,
- ConnectorEndpoint.Front,
- DEFAULT_FRONT_ENDPOINT_STYLE
- );
- const selectedEndPointStyle = getMostCommonEndpointStyle(
- elements,
- ConnectorEndpoint.Rear,
- DEFAULT_REAR_ENDPOINT_STYLE
- );
- const enableCustomColor = this.edgeless.doc
- .get(FeatureFlagService)
- .getFlag('enable_color_picker');
-
- return join(
- [
- html`
-
-
- ${LineStylesPanel({
- selectedLineSize: selectedLineSize,
- selectedLineStyle: selectedLineStyle,
- onClick: this._setConnectorStroke,
- })}
-
-
-
- `,
-
- html`
-
- ${choose(selectedRough, STYLE_CHOOSE)}${SmallArrowDownIcon}
-
- `}
- >
-
- ${repeat(
- STYLE_LIST,
- item => item.name,
- ({ name, value, icon }) => html`
- this._setConnectorRough(value)}
- >
- ${icon}
-
- `
- )}
-
-
- `,
-
- html`
-
- ${this._getEndpointIcon(
- FRONT_ENDPOINT_STYLE_LIST,
- selectedStartPointStyle
- )}${SmallArrowDownIcon}
-
- `}
- >
-
- ${repeat(
- FRONT_ENDPOINT_STYLE_LIST,
- item => item.value,
- ({ value, icon }) => html`
-
- this._setConnectorPointStyle(
- ConnectorEndpoint.Front,
- value
- )}
- >
- ${icon}
-
- `
- )}
-
-
-
-
- this._flipEndpointStyle(
- selectedStartPointStyle,
- selectedEndPointStyle
- )}
- >
- ${FlipDirectionIcon()}
-
-
-
- ${this._getEndpointIcon(
- REAR_ENDPOINT_STYLE_LIST,
- selectedEndPointStyle
- )}${SmallArrowDownIcon}
-
- `}
- >
-
- ${repeat(
- REAR_ENDPOINT_STYLE_LIST,
- item => item.value,
- ({ value, icon }) => html`
-
- this._setConnectorPointStyle(
- ConnectorEndpoint.Rear,
- value
- )}
- >
- ${icon}
-
- `
- )}
-
-
-
-
- ${choose(selectedMode, MODE_CHOOSE)}${SmallArrowDownIcon}
-
- `}
- >
-
- ${repeat(
- MODE_LIST,
- item => item.name,
- ({ name, value, icon }) => html`
- this._setConnectorMode(value)}
- >
- ${icon}
-
- `
- )}
-
-
- `,
-
- choose | typeof nothing>(
- this._showAddButtonOrTextMenu(),
- [
- [
- 'button',
- () => html`
-
- ${AddTextIcon()}
-
- `,
- ],
- [
- 'menu',
- () => html`
-
- `,
- ],
- ['nothing', () => nothing],
- ]
- ),
- ].filter(button => button !== nothing),
- renderToolbarSeparator
- );
- }
-
- @property({ attribute: false })
- accessor edgeless!: EdgelessRootBlockComponent;
-
- @property({ attribute: false })
- accessor elements: ConnectorElementModel[] = [];
-
- @query('edgeless-color-picker-button.stroke-color')
- accessor strokeColorButton!: EdgelessColorPickerButton;
-}
-
-export function renderConnectorButton(
- edgeless: EdgelessRootBlockComponent,
- elements?: ConnectorElementModel[]
-) {
- if (!elements?.length) return nothing;
-
- return html`
-
-
- `;
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-edgeless-text-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-edgeless-text-button.ts
deleted file mode 100644
index 6ddb855aa7..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-edgeless-text-button.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { EdgelessTextBlockModel } from '@blocksuite/affine-model';
-import { html, nothing } from 'lit';
-
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-
-export function renderChangeEdgelessTextButton(
- edgeless: EdgelessRootBlockComponent,
- elements?: EdgelessTextBlockModel[]
-) {
- if (!elements?.length) return nothing;
-
- return html`
-
- `;
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-embed-card-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-embed-card-button.ts
deleted file mode 100644
index bbf335ec69..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-embed-card-button.ts
+++ /dev/null
@@ -1,912 +0,0 @@
-import {} from '@blocksuite/affine-block-bookmark';
-import {
- EmbedLinkedDocBlockComponent,
- EmbedSyncedDocBlockComponent,
- getDocContentWithMaxLength,
- getEmbedCardIcons,
-} from '@blocksuite/affine-block-embed';
-import {
- EdgelessCRUDIdentifier,
- reassociateConnectorsCommand,
-} from '@blocksuite/affine-block-surface';
-import { toggleEmbedCardEditModal } from '@blocksuite/affine-components/embed-card-modal';
-import {
- CaptionIcon,
- CopyIcon,
- EditIcon,
- ExpandFullSmallIcon,
- OpenIcon,
- PaletteIcon,
-} from '@blocksuite/affine-components/icons';
-import {
- notifyLinkedDocClearedAliases,
- notifyLinkedDocSwitchedToCard,
- notifyLinkedDocSwitchedToEmbed,
-} from '@blocksuite/affine-components/notification';
-import { isPeekable, peek } from '@blocksuite/affine-components/peek';
-import { toast } from '@blocksuite/affine-components/toast';
-import {
- type MenuItem,
- renderToolbarSeparator,
-} from '@blocksuite/affine-components/toolbar';
-import {
- type AliasInfo,
- BookmarkStyles,
- type BuiltInEmbedModel,
- type EmbedCardStyle,
- isInternalEmbedModel,
-} from '@blocksuite/affine-model';
-import {
- EMBED_CARD_HEIGHT,
- EMBED_CARD_WIDTH,
-} from '@blocksuite/affine-shared/consts';
-import {
- EmbedOptionProvider,
- type EmbedOptions,
- GenerateDocUrlProvider,
- type GenerateDocUrlService,
- type LinkEventType,
- OpenDocExtensionIdentifier,
- type OpenDocMode,
- type TelemetryEvent,
- TelemetryProvider,
- ThemeProvider,
-} from '@blocksuite/affine-shared/services';
-import { getHostName, referenceToNode } from '@blocksuite/affine-shared/utils';
-import type { BlockStdScope } from '@blocksuite/block-std';
-import { Bound } from '@blocksuite/global/gfx';
-import { WithDisposable } from '@blocksuite/global/lit';
-import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
-import { property, state } from 'lit/decorators.js';
-import { ifDefined } from 'lit/directives/if-defined.js';
-import { join } from 'lit/directives/join.js';
-import { repeat } from 'lit/directives/repeat.js';
-
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-import {
- isBookmarkBlock,
- isEmbedGithubBlock,
- isEmbedHtmlBlock,
- isEmbedLinkedDocBlock,
- isEmbedSyncedDocBlock,
-} from '../../edgeless/utils/query.js';
-import type { BuiltInEmbedBlockComponent } from '../../utils/types';
-import { SmallArrowDownIcon } from './icons.js';
-
-export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
- static override styles = css`
- .affine-link-preview {
- display: flex;
- justify-content: flex-start;
- width: 140px;
- padding: var(--1, 0px);
- border-radius: var(--1, 0px);
- opacity: var(--add, 1);
- user-select: none;
- cursor: pointer;
-
- color: var(--affine-link-color);
- font-feature-settings:
- 'clig' off,
- 'liga' off;
- font-family: var(--affine-font-family);
- font-size: var(--affine-font-sm);
- font-style: normal;
- font-weight: 400;
- text-decoration: none;
- text-wrap: nowrap;
- }
-
- .affine-link-preview > span {
- display: inline-block;
- -webkit-line-clamp: 1;
- -webkit-box-orient: vertical;
-
- text-overflow: ellipsis;
- overflow: hidden;
- opacity: var(--add, 1);
- }
-
- editor-icon-button.doc-title .label {
- max-width: 110px;
- display: inline-block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- user-select: none;
- cursor: pointer;
- color: var(--affine-link-color);
- font-feature-settings:
- 'clig' off,
- 'liga' off;
- font-family: var(--affine-font-family);
- font-size: var(--affine-font-sm);
- font-style: normal;
- font-weight: 400;
- text-decoration: none;
- text-wrap: nowrap;
- }
- `;
-
- get crud() {
- return this.edgeless.std.get(EdgelessCRUDIdentifier);
- }
-
- private readonly _convertToCardView = () => {
- if (this._isCardView) {
- return;
- }
-
- const block = this._blockComponent;
- if (block && 'convertToCard' in block) {
- block.convertToCard();
- return;
- }
-
- if (!('url' in this.model)) {
- return;
- }
-
- const { xywh, style, caption } = this.model.props;
- const { id, url } = this.model;
-
- let targetFlavour = 'affine:bookmark',
- targetStyle = style;
-
- if (this._embedOptions && this._embedOptions.viewType === 'card') {
- const { flavour, styles } = this._embedOptions;
- targetFlavour = flavour;
- targetStyle = styles.includes(style) ? style : styles[0];
- } else {
- targetStyle = BookmarkStyles.includes(style) ? style : BookmarkStyles[0];
- }
-
- const bound = Bound.deserialize(xywh);
- bound.w = EMBED_CARD_WIDTH[targetStyle];
- bound.h = EMBED_CARD_HEIGHT[targetStyle];
-
- const newId = this.crud.addBlock(
- targetFlavour,
- { url, xywh: bound.serialize(), style: targetStyle, caption },
- this.edgeless.surface.model
- );
-
- this.std.command.exec(reassociateConnectorsCommand, {
- oldId: id,
- newId,
- });
-
- this.edgeless.service.selection.set({
- editing: false,
- elements: [newId],
- });
- this._doc.deleteBlock(this.model);
- };
-
- private readonly _convertToEmbedView = () => {
- if (this._isEmbedView) {
- return;
- }
-
- const block = this._blockComponent;
- if (block && 'convertToEmbed' in block) {
- const referenceInfo = block.referenceInfo$.peek();
-
- block.convertToEmbed();
-
- if (referenceInfo.title || referenceInfo.description)
- notifyLinkedDocSwitchedToEmbed(this.std);
-
- return;
- }
-
- if (!('url' in this.model)) {
- return;
- }
-
- if (!this._embedOptions) return;
-
- const { flavour, styles } = this._embedOptions;
-
- const { id, url, xywh } = this.model;
- const { style } = this.model.props;
-
- const targetStyle = styles.includes(style) ? style : styles[0];
-
- const bound = Bound.deserialize(xywh);
- bound.w = EMBED_CARD_WIDTH[targetStyle];
- bound.h = EMBED_CARD_HEIGHT[targetStyle];
-
- const newId = this.crud.addBlock(
- flavour,
- {
- url,
- xywh: bound.serialize(),
- style: targetStyle,
- },
- this.edgeless.surface.model
- );
- if (!newId) return;
-
- this.std.command.exec(reassociateConnectorsCommand, {
- oldId: id,
- newId,
- });
-
- this.edgeless.service.selection.set({
- editing: false,
- elements: [newId],
- });
- this._doc.deleteBlock(this.model);
- };
-
- private readonly _copyUrl = () => {
- let url!: ReturnType;
-
- if ('url' in this.model.props) {
- url = this.model.props.url;
- } else if (isInternalEmbedModel(this.model)) {
- url = this.std
- .getOptional(GenerateDocUrlProvider)
- ?.generateDocUrl(this.model.props.pageId, this.model.props.params);
- }
-
- if (!url) return;
-
- navigator.clipboard.writeText(url).catch(console.error);
- toast(this.std.host, 'Copied link to clipboard');
- this.edgeless.service.selection.clear();
-
- track(this.std, this.model, this._viewType, 'CopiedLink', {
- control: 'copy link',
- });
- };
-
- private _embedOptions: EmbedOptions | null = null;
-
- private readonly _getScale = () => {
- if ('scale' in this.model.props) {
- return this.model.props.scale ?? 1;
- } else if (isEmbedHtmlBlock(this.model)) {
- return 1;
- }
-
- const bound = Bound.deserialize(this.model.xywh);
- return bound.h / EMBED_CARD_HEIGHT[this.model.props.style];
- };
-
- private readonly _open = ({ openMode }: { openMode?: OpenDocMode } = {}) => {
- this._blockComponent?.open({ openMode });
- };
-
- private readonly _openEditPopup = (e: MouseEvent) => {
- e.stopPropagation();
-
- if (isEmbedHtmlBlock(this.model)) return;
-
- this.std.selection.clear();
-
- const originalDocInfo = this._originalDocInfo;
-
- toggleEmbedCardEditModal(
- this.std.host,
- this.model,
- this._viewType,
- originalDocInfo,
- (std, component) => {
- if (
- isEmbedLinkedDocBlock(this.model) &&
- component instanceof EmbedLinkedDocBlockComponent
- ) {
- component.refreshData();
-
- notifyLinkedDocClearedAliases(std);
- }
- },
- (std, component, props) => {
- if (
- isEmbedSyncedDocBlock(this.model) &&
- component instanceof EmbedSyncedDocBlockComponent
- ) {
- component.convertToCard(props);
-
- notifyLinkedDocSwitchedToCard(std);
- } else {
- this.model.doc.updateBlock(this.model, props);
- component.requestUpdate();
- }
- }
- );
-
- track(this.std, this.model, this._viewType, 'OpenedAliasPopup', {
- control: 'edit',
- });
- };
-
- private readonly _peek = () => {
- if (!this._blockComponent) return;
- peek(this._blockComponent);
- };
-
- private readonly _setCardStyle = (style: EmbedCardStyle) => {
- const bounds = Bound.deserialize(this.model.xywh);
- bounds.w = EMBED_CARD_WIDTH[style];
- bounds.h = EMBED_CARD_HEIGHT[style];
- const xywh = bounds.serialize();
- this.model.doc.updateBlock(this.model, { style, xywh });
-
- track(this.std, this.model, this._viewType, 'SelectedCardStyle', {
- control: 'select card style',
- type: style,
- });
- };
-
- private readonly _setEmbedScale = (scale: number) => {
- if (isEmbedHtmlBlock(this.model)) return;
-
- const bound = Bound.deserialize(this.model.xywh);
- if ('scale' in this.model.props) {
- const oldScale = this.model.props.scale ?? 1;
- const ratio = scale / oldScale;
- bound.w *= ratio;
- bound.h *= ratio;
- const xywh = bound.serialize();
- this.model.doc.updateBlock(this.model, { scale, xywh });
- } else {
- bound.h = EMBED_CARD_HEIGHT[this.model.props.style] * scale;
- bound.w = EMBED_CARD_WIDTH[this.model.props.style] * scale;
- const xywh = bound.serialize();
- this.model.doc.updateBlock(this.model, { xywh });
- }
- this._embedScale = scale;
-
- track(this.std, this.model, this._viewType, 'SelectedCardScale', {
- control: 'select card scale',
- type: `${scale}`,
- });
- };
-
- private readonly _toggleCardScaleSelector = (e: Event) => {
- const opened = (e as CustomEvent).detail;
- if (!opened) return;
-
- track(this.std, this.model, this._viewType, 'OpenedCardScaleSelector', {
- control: 'switch card scale',
- });
- };
-
- private readonly _toggleCardStyleSelector = (e: Event) => {
- const opened = (e as CustomEvent).detail;
- if (!opened) return;
-
- track(this.std, this.model, this._viewType, 'OpenedCardStyleSelector', {
- control: 'switch card style',
- });
- };
-
- private readonly _toggleViewSelector = (e: Event) => {
- const opened = (e as CustomEvent).detail;
- if (!opened) return;
-
- track(this.std, this.model, this._viewType, 'OpenedViewSelector', {
- control: 'switch view',
- });
- };
-
- private readonly _trackViewSelected = (type: string) => {
- track(this.std, this.model, this._viewType, 'SelectedView', {
- control: 'select view',
- type: `${type} view`,
- });
- };
-
- private get _blockComponent() {
- const blockSelection =
- this.edgeless.service.selection.surfaceSelections.filter(sel =>
- sel.elements.includes(this.model.id)
- );
- if (blockSelection.length !== 1) {
- return;
- }
-
- const blockComponent = this.std.view.getBlock(
- blockSelection[0].blockId
- ) as BuiltInEmbedBlockComponent | null;
-
- if (!blockComponent) return;
-
- return blockComponent;
- }
-
- private get _canConvertToEmbedView() {
- const block = this._blockComponent;
-
- return (
- (block && 'convertToEmbed' in block) ||
- this._embedOptions?.viewType === 'embed'
- );
- }
-
- private get _canShowCardStylePanel() {
- return (
- isBookmarkBlock(this.model) ||
- isEmbedGithubBlock(this.model) ||
- isEmbedLinkedDocBlock(this.model)
- );
- }
-
- private get _canShowFullScreenButton() {
- return isEmbedHtmlBlock(this.model);
- }
-
- private get _canShowUrlOptions() {
- return (
- 'url' in this.model &&
- (isBookmarkBlock(this.model) ||
- isEmbedGithubBlock(this.model) ||
- isEmbedLinkedDocBlock(this.model))
- );
- }
-
- private get _doc() {
- return this.model.doc;
- }
-
- private get _embedViewButtonDisabled() {
- if (this._doc.readonly) {
- return true;
- }
- return (
- isEmbedLinkedDocBlock(this.model) &&
- (referenceToNode(this.model.props) ||
- !!this._blockComponent?.closest('affine-embed-synced-doc-block') ||
- this.model.props.pageId === this._doc.id)
- );
- }
-
- private get _getCardStyleOptions(): {
- style: EmbedCardStyle;
- Icon: TemplateResult<1>;
- tooltip: string;
- }[] {
- const theme = this.std.get(ThemeProvider).theme;
- const {
- EmbedCardHorizontalIcon,
- EmbedCardListIcon,
- EmbedCardVerticalIcon,
- EmbedCardCubeIcon,
- } = getEmbedCardIcons(theme);
- return [
- {
- style: 'horizontal',
- Icon: EmbedCardHorizontalIcon,
- tooltip: 'Large horizontal style',
- },
- {
- style: 'list',
- Icon: EmbedCardListIcon,
- tooltip: 'Small horizontal style',
- },
- {
- style: 'vertical',
- Icon: EmbedCardVerticalIcon,
- tooltip: 'Large vertical style',
- },
- {
- style: 'cube',
- Icon: EmbedCardCubeIcon,
- tooltip: 'Small vertical style',
- },
- ];
- }
-
- private get _isCardView() {
- if (isBookmarkBlock(this.model) || isEmbedLinkedDocBlock(this.model)) {
- return true;
- }
- return this._embedOptions?.viewType === 'card';
- }
-
- private get _isEmbedView() {
- return (
- !isBookmarkBlock(this.model) &&
- (isEmbedSyncedDocBlock(this.model) ||
- this._embedOptions?.viewType === 'embed')
- );
- }
-
- get _originalDocInfo(): AliasInfo | undefined {
- const model = this.model;
- const doc = isInternalEmbedModel(model)
- ? this.std.workspace.getDoc(model.props.pageId)
- : null;
-
- if (doc) {
- const title = doc.meta?.title;
- const description = isEmbedLinkedDocBlock(model)
- ? getDocContentWithMaxLength(doc)
- : undefined;
- return { title, description };
- }
-
- return undefined;
- }
-
- get _originalDocTitle() {
- const model = this.model;
- const doc = isInternalEmbedModel(model)
- ? this.std.workspace.getDoc(model.props.pageId)
- : null;
-
- return doc?.meta?.title || 'Untitled';
- }
-
- private get _viewType(): 'inline' | 'embed' | 'card' {
- if (this._isCardView) {
- return 'card';
- }
-
- if (this._isEmbedView) {
- return 'embed';
- }
-
- // unreachable
- return 'inline';
- }
-
- private get std() {
- return this.edgeless.std;
- }
-
- private _openMenuButton() {
- const openDocConfig = this.std.get(OpenDocExtensionIdentifier);
- const buttons: MenuItem[] = openDocConfig.items
- .map(item => {
- if (
- item.type === 'open-in-center-peek' &&
- this._blockComponent &&
- !isPeekable(this._blockComponent)
- ) {
- return null;
- }
-
- if (
- !(
- isEmbedLinkedDocBlock(this.model) ||
- isEmbedSyncedDocBlock(this.model)
- )
- ) {
- return null;
- }
-
- return {
- label: item.label,
- type: item.type,
- icon: item.icon,
- disabled:
- this.model.props.pageId === this._doc.id &&
- item.type === 'open-in-active-view',
- action: () => {
- if (item.type === 'open-in-center-peek') {
- this._peek();
- } else {
- this._open({ openMode: item.type });
- }
- },
- };
- })
- .filter(item => item !== null);
-
- // todo: abstract this?
- if (this._canShowFullScreenButton) {
- buttons.push({
- type: 'open-this-doc',
- label: 'Open this doc',
- icon: ExpandFullSmallIcon,
- action: this._open,
- });
- }
-
- if (buttons.length === 0) {
- return nothing;
- }
-
- return html`
-
- ${OpenIcon}${SmallArrowDownIcon}
-
- `}
- >
-
- ${repeat(
- buttons,
- button => button.label,
- ({ label, icon, action, disabled }) => html`
-
- ${icon}${label}
-
- `
- )}
-
-
- `;
- }
-
- private _showCaption() {
- this._blockComponent?.captionEditor?.show();
-
- track(this.std, this.model, this._viewType, 'OpenedCaptionEditor', {
- control: 'add caption',
- });
- }
-
- private _viewSelector() {
- if (this._canConvertToEmbedView || this._isEmbedView) {
- const buttons = [
- {
- type: 'card',
- label: 'Card view',
- action: () => this._convertToCardView(),
- disabled: this.model.doc.readonly,
- },
- {
- type: 'embed',
- label: 'Embed view',
- action: () => this._convertToEmbedView(),
- disabled: this.model.doc.readonly || this._embedViewButtonDisabled,
- },
- ];
-
- return html`
-
-
- ${this._viewType}
- view
-
- ${SmallArrowDownIcon}
-
- `}
- @toggle=${this._toggleViewSelector}
- >
-
- ${repeat(
- buttons,
- button => button.type,
- ({ type, label, action, disabled }) => html`
- {
- action();
- this._trackViewSelected(type);
- }}
- >
- ${label}
-
- `
- )}
-
-
- `;
- }
-
- return nothing;
- }
-
- override connectedCallback() {
- super.connectedCallback();
- this._embedScale = this._getScale();
- }
-
- override render() {
- const model = this.model;
- const isHtmlBlockModel = isEmbedHtmlBlock(model);
-
- if ('url' in this.model.props) {
- this._embedOptions = this.std
- .get(EmbedOptionProvider)
- .getEmbedBlockOptions(this.model.props.url);
- }
-
- const buttons = [
- this._openMenuButton(),
-
- this._canShowUrlOptions && 'url' in model.props
- ? html`
-
- ${getHostName(model.props.url)}
-
- `
- : nothing,
-
- // internal embed model
- isEmbedLinkedDocBlock(model) && model.props.title
- ? html`
-
- ${this._originalDocTitle}
-
- `
- : nothing,
-
- isHtmlBlockModel
- ? nothing
- : html`
-
- ${CopyIcon}
-
-
-
- ${EditIcon}
-
- `,
-
- this._viewSelector(),
-
- 'style' in model && this._canShowCardStylePanel
- ? html`
-
- ${PaletteIcon}
-
- `}
- @toggle=${this._toggleCardStyleSelector}
- >
-
-
-
- `
- : nothing,
-
- 'caption' in model
- ? html`
-
- ${CaptionIcon}
-
- `
- : nothing,
-
- this.quickConnectButton,
-
- isHtmlBlockModel
- ? nothing
- : html`
-
-
- ${Math.round(this._embedScale * 100) + '%'}
-
- ${SmallArrowDownIcon}
-
- `}
- @toggle=${this._toggleCardScaleSelector}
- >
-
-
- `,
- ];
-
- return join(
- buttons.filter(button => button !== nothing),
- renderToolbarSeparator
- );
- }
-
- @state()
- private accessor _embedScale = 1;
-
- @property({ attribute: false })
- accessor edgeless!: EdgelessRootBlockComponent;
-
- @property({ attribute: false })
- accessor model!: BuiltInEmbedModel;
-
- @property({ attribute: false })
- accessor quickConnectButton!: TemplateResult<1> | typeof nothing;
-}
-
-export function renderEmbedButton(
- edgeless: EdgelessRootBlockComponent,
- models?: EdgelessChangeEmbedCardButton['model'][],
- quickConnectButton?: TemplateResult<1>[]
-) {
- if (models?.length !== 1) return nothing;
-
- return html`
-
- `;
-}
-
-function track(
- std: BlockStdScope,
- model: BuiltInEmbedModel,
- viewType: string,
- event: LinkEventType,
- props: Partial
-) {
- std.getOptional(TelemetryProvider)?.track(event, {
- segment: 'toolbar',
- page: 'whiteboard editor',
- module: 'element toolbar',
- type: `${viewType} view`,
- category: isInternalEmbedModel(model) ? 'linked doc' : 'link',
- ...props,
- });
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-frame-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-frame-button.ts
deleted file mode 100644
index ea23ffa52c..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-frame-button.ts
+++ /dev/null
@@ -1,215 +0,0 @@
-import { EdgelessFrameManagerIdentifier } from '@blocksuite/affine-block-frame';
-import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
-import type {
- EdgelessColorPickerButton,
- PickColorEvent,
-} from '@blocksuite/affine-components/color-picker';
-import { packColor } from '@blocksuite/affine-components/color-picker';
-import { toast } from '@blocksuite/affine-components/toast';
-import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
-import {
- type ColorScheme,
- DEFAULT_NOTE_HEIGHT,
- type FrameBlockModel,
- NoteBlockModel,
- NoteDisplayMode,
- resolveColor,
-} from '@blocksuite/affine-model';
-import { FeatureFlagService } from '@blocksuite/affine-shared/services';
-import { matchModels } from '@blocksuite/affine-shared/utils';
-import { deserializeXYWH, serializeXYWH } from '@blocksuite/global/gfx';
-import { WithDisposable } from '@blocksuite/global/lit';
-import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit';
-import { html, LitElement, nothing } from 'lit';
-import { property, query } from 'lit/decorators.js';
-import { join } from 'lit/directives/join.js';
-import countBy from 'lodash-es/countBy';
-import maxBy from 'lodash-es/maxBy';
-
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-import { mountFrameTitleEditor } from '../../edgeless/utils/text.js';
-
-function getMostCommonColor(
- elements: FrameBlockModel[],
- colorScheme: ColorScheme
-): string {
- const colors = countBy(elements, (ele: FrameBlockModel) =>
- resolveColor(ele.props.background, colorScheme)
- );
- const max = maxBy(Object.entries(colors), ([_k, count]) => count);
- return max ? (max[0] as string) : 'transparent';
-}
-
-export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
- get crud() {
- return this.edgeless.std.get(EdgelessCRUDIdentifier);
- }
-
- pickColor = (e: PickColorEvent) => {
- const field = 'background';
-
- if (e.type === 'pick') {
- const color = e.detail.value;
- this.frames.forEach(ele => {
- const props = packColor(field, color);
- this.crud.updateElement(ele.id, props);
- });
- return;
- }
-
- this.frames.forEach(ele =>
- ele[e.type === 'start' ? 'stash' : 'pop'](field)
- );
- };
-
- 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 =>
- matchModels(model, [NoteBlockModel]) &&
- model.props.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');
- }
-
- 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);
- const enableCustomColor = this.edgeless.doc
- .get(FeatureFlagService)
- .getFlag('enable_color_picker');
-
- return join(
- [
- onlyOne
- ? html`
-
- ${PageIcon()}
- Insert into Page
-
- `
- : nothing,
-
- onlyOne
- ? html`
-
- mountFrameTitleEditor(this.frames[0], this.edgeless)}
- >
- ${EditIcon()}
-
- `
- : nothing,
-
- html`
- {
- this.edgeless.doc.captureSync();
- const frameMgr = this.edgeless.std.get(
- EdgelessFrameManagerIdentifier
- );
- frames.forEach(frame =>
- frameMgr.removeAllChildrenFromFrame(frame)
- );
- frames.forEach(frame => {
- this.edgeless.service.removeElement(frame);
- });
- this.edgeless.service.selection.clear();
- }}
- >
- ${UngroupIcon()}
-
- `,
-
- html`
-
-
- `,
- ].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`
-
- `;
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-group-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-group-button.ts
deleted file mode 100644
index b2eb6d8d95..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-group-button.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import { toast } from '@blocksuite/affine-components/toast';
-import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
-import type { GroupElementModel } from '@blocksuite/affine-model';
-import {
- DEFAULT_NOTE_HEIGHT,
- NoteBlockModel,
- NoteDisplayMode,
-} from '@blocksuite/affine-model';
-import { matchModels } from '@blocksuite/affine-shared/utils';
-import { deserializeXYWH, serializeXYWH } from '@blocksuite/global/gfx';
-import { WithDisposable } from '@blocksuite/global/lit';
-import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit';
-import { html, LitElement, nothing } from 'lit';
-import { property } from 'lit/decorators.js';
-import { join } from 'lit/directives/join.js';
-
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-import { mountGroupTitleEditor } from '../../edgeless/utils/text.js';
-
-export class EdgelessChangeGroupButton extends WithDisposable(LitElement) {
- private _insertIntoPage() {
- if (!this.edgeless.doc.root) return;
-
- const rootModel = this.edgeless.doc.root;
- const notes = rootModel.children.filter(
- model =>
- matchModels(model, [NoteBlockModel]) &&
- model.props.displayMode !== NoteDisplayMode.EdgelessOnly
- );
- const lastNote = notes[notes.length - 1];
- const referenceGroup = this.groups[0];
-
- let targetParent = lastNote?.id;
-
- if (!lastNote) {
- const targetXYWH = deserializeXYWH(referenceGroup.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.groups[0].id,
- refFlavour: 'group',
- },
- targetParent
- );
-
- toast(this.edgeless.host, 'Group has been inserted into page');
- }
-
- protected override render() {
- const { groups } = this;
- const onlyOne = groups.length === 1;
-
- return join(
- [
- onlyOne
- ? html`
-
- ${PageIcon()}
- Insert into Page
-
- `
- : nothing,
-
- onlyOne
- ? html`
- mountGroupTitleEditor(groups[0], this.edgeless)}
- >
- ${EditIcon()}
-
- `
- : nothing,
-
- html`
-
- groups.forEach(group => this.edgeless.service.ungroup(group))}
- >
- ${UngroupIcon()}
-
- `,
- ].filter(button => button !== nothing),
- renderToolbarSeparator
- );
- }
-
- @property({ attribute: false })
- accessor edgeless!: EdgelessRootBlockComponent;
-
- @property({ attribute: false })
- accessor groups!: GroupElementModel[];
-}
-
-export function renderGroupButton(
- edgeless: EdgelessRootBlockComponent,
- groups?: GroupElementModel[]
-) {
- if (!groups?.length) return nothing;
-
- return html`
-
-
- `;
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-image-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-image-button.ts
deleted file mode 100644
index 9a17ad3c8c..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-image-button.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import {
- downloadImageBlob,
- type ImageBlockComponent,
-} from '@blocksuite/affine-block-image';
-import { CaptionIcon, DownloadIcon } from '@blocksuite/affine-components/icons';
-import type { ImageBlockModel } from '@blocksuite/affine-model';
-import { WithDisposable } from '@blocksuite/global/lit';
-import { html, LitElement, nothing } from 'lit';
-import { property } from 'lit/decorators.js';
-
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-
-export class EdgelessChangeImageButton extends WithDisposable(LitElement) {
- private readonly _download = () => {
- if (!this._blockComponent) return;
- downloadImageBlob(this._blockComponent).catch(console.error);
- };
-
- private readonly _showCaption = () => {
- this._blockComponent?.captionEditor?.show();
- };
-
- private get _blockComponent() {
- const blockSelection =
- this.edgeless.service.selection.surfaceSelections.filter(sel =>
- sel.elements.includes(this.model.id)
- );
- if (blockSelection.length !== 1) {
- return;
- }
-
- const block = this.edgeless.std.view.getBlock(
- blockSelection[0].blockId
- ) as ImageBlockComponent | null;
-
- return block;
- }
-
- private get _doc() {
- return this.model.doc;
- }
-
- override render() {
- return html`
-
- ${DownloadIcon}
-
-
-
-
-
- ${CaptionIcon}
-
- `;
- }
-
- @property({ attribute: false })
- accessor edgeless!: EdgelessRootBlockComponent;
-
- @property({ attribute: false })
- accessor model!: ImageBlockModel;
-}
-
-export function renderChangeImageButton(
- edgeless: EdgelessRootBlockComponent,
- images?: ImageBlockModel[]
-) {
- if (images?.length !== 1) return nothing;
-
- return html`
-
- `;
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-mindmap-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-mindmap-button.ts
deleted file mode 100644
index 3b303220de..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-mindmap-button.ts
+++ /dev/null
@@ -1,299 +0,0 @@
-import {
- MindmapStyleFour,
- MindmapStyleOne,
- MindmapStyleThree,
- MindmapStyleTwo,
-} from '@blocksuite/affine-block-surface';
-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 { WithDisposable } from '@blocksuite/global/lit';
-import { RadiantIcon, RightLayoutIcon, StyleIcon } from '@blocksuite/icons/lit';
-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 countBy from 'lodash-es/countBy';
-import maxBy from 'lodash-es/maxBy';
-
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-import { SmallArrowDownIcon } from './icons.js';
-
-const iconSize = { width: '20', height: '20' };
-
-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: RightLayoutIcon({
- ...iconSize,
- style: 'transform: rotate(0.5turn); transform-origin: center;',
- }),
- },
- {
- name: 'Radial',
- value: LayoutType.BALANCE,
- icon: RadiantIcon(iconSize),
- },
- {
- name: 'Right',
- value: LayoutType.RIGHT,
- icon: RightLayoutIcon(iconSize),
- },
-] 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`
-
- ${StyleIcon(iconSize)}${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}
- >
-
- `;
-}
diff --git a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-note-button.ts b/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-note-button.ts
deleted file mode 100644
index ab20001792..0000000000
--- a/blocksuite/affine/blocks/block-root/src/widgets/element-toolbar/change-note-button.ts
+++ /dev/null
@@ -1,576 +0,0 @@
-import {
- changeNoteDisplayMode,
- NoteConfigExtension,
-} from '@blocksuite/affine-block-note';
-import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
-import type {
- EdgelessColorPickerButton,
- PickColorEvent,
-} from '@blocksuite/affine-components/color-picker';
-import { packColor } from '@blocksuite/affine-components/color-picker';
-import {
- type EditorMenuButton,
- renderToolbarSeparator,
-} from '@blocksuite/affine-components/toolbar';
-import {
- type ColorScheme,
- DefaultTheme,
- type NoteBlockModel,
- NoteDisplayMode,
- resolveColor,
- type StrokeStyle,
-} from '@blocksuite/affine-model';
-import {
- FeatureFlagService,
- NotificationProvider,
- SidebarExtensionIdentifier,
- TelemetryProvider,
- ThemeProvider,
-} from '@blocksuite/affine-shared/services';
-import { EditorLifeCycleExtension } from '@blocksuite/block-std';
-import { Bound } from '@blocksuite/global/gfx';
-import { WithDisposable } from '@blocksuite/global/lit';
-import {
- AutoHeightIcon,
- CornerIcon,
- CustomizedHeightIcon,
- LineStyleIcon,
- LinkedPageIcon,
- NoteShadowDuotoneIcon,
- ScissorsIcon,
-} from '@blocksuite/icons/lit';
-import { html, LitElement, nothing, type TemplateResult } from 'lit';
-import { property, query } from 'lit/decorators.js';
-import { join } from 'lit/directives/join.js';
-import { createRef, type Ref, ref } from 'lit/directives/ref.js';
-import countBy from 'lodash-es/countBy';
-import maxBy from 'lodash-es/maxBy';
-
-import {
- type LineStyleEvent,
- LineStylesPanel,
-} from '../../edgeless/components/panel/line-styles-panel.js';
-import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
-import { SmallArrowDownIcon } from './icons.js';
-import * as styles from './styles.css';
-
-const SIZE_LIST = [
- { name: 'None', value: 0 },
- { name: 'Small', value: 8 },
- { name: 'Medium', value: 16 },
- { name: 'Large', value: 24 },
- { name: 'Huge', value: 32 },
-] as const;
-
-const DisplayModeMap = {
- [NoteDisplayMode.DocAndEdgeless]: 'Both',
- [NoteDisplayMode.EdgelessOnly]: 'Edgeless',
- [NoteDisplayMode.DocOnly]: 'Page',
-} as const satisfies Record;
-
-function getMostCommonBackground(
- elements: NoteBlockModel[],
- colorScheme: ColorScheme
-): string {
- const colors = countBy(elements, (ele: NoteBlockModel) =>
- resolveColor(ele.props.background, colorScheme)
- );
- const max = maxBy(Object.entries(colors), ([_k, count]) => count);
- return max
- ? (max[0] as string)
- : resolveColor(DefaultTheme.noteBackgrounColor, colorScheme);
-}
-
-export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
- get crud() {
- return this.edgeless.std.get(EdgelessCRUDIdentifier);
- }
-
- private readonly _setBorderRadius = (borderRadius: number) => {
- this.notes.forEach(note => {
- const props = {
- edgeless: {
- ...note.props.edgeless,
- style: {
- ...note.props.edgeless.style,
- borderRadius,
- },
- },
- };
- this.crud.updateElement(note.id, props);
- });
- };
-
- private readonly _setNoteScale = (scale: number) => {
- this.notes.forEach(note => {
- this.doc.updateBlock(note, () => {
- const bound = Bound.deserialize(note.xywh);
- const oldScale = note.props.edgeless.scale ?? 1;
- const ratio = scale / oldScale;
- bound.w *= ratio;
- bound.h *= ratio;
- const xywh = bound.serialize();
- note.xywh = xywh;
- note.props.edgeless.scale = scale;
- });
- });
- };
-
- pickColor = (e: PickColorEvent) => {
- const field = 'background';
-
- if (e.type === 'pick') {
- const color = e.detail.value;
- this.notes.forEach(element => {
- const props = packColor(field, color);
- this.crud.updateElement(element.id, props);
- });
- return;
- }
-
- this.notes.forEach(ele => ele[e.type === 'start' ? 'stash' : 'pop'](field));
- };
-
- private get _advancedVisibilityEnabled() {
- return this.doc
- .get(FeatureFlagService)
- .getFlag('enable_advanced_block_visibility');
- }
-
- private get doc() {
- return this.edgeless.doc;
- }
-
- private _getScaleLabel(scale: number) {
- return Math.round(scale * 100) + '%';
- }
-
- private _handleNoteSlicerButtonClick() {
- const surfaceService = this.edgeless.service;
- if (!surfaceService) return;
-
- this.edgeless.slots.toggleNoteSlicer.next();
- }
-
- private _setCollapse() {
- this.doc.captureSync();
- this.notes.forEach(note => {
- const { collapse, collapsedHeight } = note.props.edgeless;
-
- if (collapse) {
- this.doc.updateBlock(note, () => {
- note.props.edgeless.collapse = false;
- });
- } else if (collapsedHeight) {
- const { xywh, edgeless } = note.props;
- const bound = Bound.deserialize(xywh);
- bound.h = collapsedHeight * (edgeless.scale ?? 1);
- this.doc.updateBlock(note, () => {
- note.props.edgeless.collapse = true;
- note.props.xywh = bound.serialize();
- });
- }
- });
- this.requestUpdate();
- }
-
- private _setDisplayMode(note: NoteBlockModel, newMode: NoteDisplayMode) {
- const oldMode = note.props.displayMode;
- this.edgeless.std.command.exec(changeNoteDisplayMode, {
- noteId: note.id,
- mode: newMode,
- stopCapture: true,
- });
-
- // if change note to page only, should clear the selection
- if (newMode === NoteDisplayMode.DocOnly) {
- this.edgeless.service.selection.clear();
- }
-
- const abortController = new AbortController();
- const clear = () => {
- this.doc.history.off('stack-item-added', addHandler);
- this.doc.history.off('stack-item-popped', popHandler);
- disposable.unsubscribe();
- };
- const closeNotify = () => {
- abortController.abort();
- clear();
- };
-
- const addHandler = this.doc.history.on('stack-item-added', closeNotify);
- const popHandler = this.doc.history.on('stack-item-popped', closeNotify);
- const disposable = this.edgeless.std
- .get(EditorLifeCycleExtension)
- .slots.unmounted.subscribe(closeNotify);
-
- const undo = () => {
- this.doc.undo();
- closeNotify();
- };
-
- const viewInToc = () => {
- const sidebar = this.edgeless.std.getOptional(SidebarExtensionIdentifier);
- sidebar?.open('outline');
- closeNotify();
- };
-
- const title =
- newMode !== NoteDisplayMode.EdgelessOnly
- ? 'Note displayed in Page Mode'
- : 'Note removed from Page Mode';
- const message =
- newMode !== NoteDisplayMode.EdgelessOnly
- ? 'Content added to your page.'
- : 'Content removed from your page.';
-
- const notification = this.edgeless.std.getOptional(NotificationProvider);
- notification?.notify({
- title: title,
- message: `${message}. Find it in the TOC for quick navigation.`,
- accent: 'success',
- duration: 5 * 1000,
- footer: html``,
- abort: abortController.signal,
- onClose: () => {
- clear();
- },
- });
-
- this.edgeless.std
- .getOptional(TelemetryProvider)
- ?.track('NoteDisplayModeChanged', {
- page: 'whiteboard editor',
- segment: 'element toolbar',
- module: 'element toolbar',
- control: 'display mode',
- type: 'note',
- other: `from ${oldMode} to ${newMode}`,
- });
- }
-
- private _setShadowType(shadowType: string) {
- this.notes.forEach(note => {
- const props = {
- edgeless: {
- ...note.props.edgeless,
- style: {
- ...note.props.edgeless.style,
- shadowType,
- },
- },
- };
- this.crud.updateElement(note.id, props);
- });
- }
-
- private _setStrokeStyle(borderStyle: StrokeStyle) {
- this.notes.forEach(note => {
- const props = {
- edgeless: {
- ...note.props.edgeless,
- style: {
- ...note.props.edgeless.style,
- borderStyle,
- },
- },
- };
- this.crud.updateElement(note.id, props);
- });
- }
-
- private _setStrokeWidth(borderSize: number) {
- this.notes.forEach(note => {
- const props = {
- edgeless: {
- ...note.props.edgeless,
- style: {
- ...note.props.edgeless.style,
- borderSize,
- },
- },
- };
- this.crud.updateElement(note.id, props);
- });
- }
-
- private _setStyles({ type, value }: LineStyleEvent) {
- if (type === 'size') {
- this._setStrokeWidth(value);
- return;
- }
- if (type === 'lineStyle') {
- this._setStrokeStyle(value);
- }
- }
-
- override render() {
- const len = this.notes.length;
- const note = this.notes[0];
- const { edgeless, displayMode } = note.props;
- const { shadowType, borderRadius, borderSize, borderStyle } =
- edgeless.style;
- const colorScheme = this.edgeless.surface.renderer.getColorScheme();
- const background = getMostCommonBackground(this.notes, colorScheme);
-
- const { collapse } = edgeless;
- const scale = edgeless.scale ?? 1;
- const currentMode = DisplayModeMap[displayMode];
- const onlyOne = len === 1;
- const isDocOnly = displayMode === NoteDisplayMode.DocOnly;
-
- const hasPageBlockHeader = !!this.edgeless.std.getOptional(
- NoteConfigExtension.identifier
- )?.edgelessNoteHeader;
-
- const enableCustomColor = this.edgeless.doc
- .get(FeatureFlagService)
- .getFlag('enable_color_picker');
-
- const theme = this.edgeless.std.get(ThemeProvider).theme;
- const buttonIconSize = { width: '20px', height: '20px' };
- const buttons = [
- onlyOne && this._advancedVisibilityEnabled
- ? html`
- Show in
-
- ${currentMode}
- ${SmallArrowDownIcon}
-
- `}
- >
-
- this._setDisplayMode(note, newMode)}
- >
-
-
- `
- : nothing,
-
- onlyOne && !note.isPageBlock() && !this._advancedVisibilityEnabled
- ? html`
- this._setDisplayMode(
- note,
- displayMode === NoteDisplayMode.EdgelessOnly
- ? NoteDisplayMode.DocAndEdgeless
- : NoteDisplayMode.EdgelessOnly
- )}
- >
- ${LinkedPageIcon(buttonIconSize)}
- ${displayMode === NoteDisplayMode.EdgelessOnly
- ? 'Display In Page'
- : 'Displayed In Page'}
- `
- : nothing,
-
- isDocOnly
- ? nothing
- : html`
-
-
- `,
-
- isDocOnly
- ? nothing
- : html`
-
- ${NoteShadowDuotoneIcon(buttonIconSize)}${SmallArrowDownIcon}
-
- `}
- >
- this._setShadowType(value)}
- >
-
-
-
-
- ${LineStyleIcon(buttonIconSize)}${SmallArrowDownIcon}
-
- `}
- >
-
- ${LineStylesPanel({
- selectedLineSize: borderSize,
- selectedLineStyle: borderStyle,
- onClick: event => this._setStyles(event),
- })}
-
-
-
-
- ${CornerIcon(buttonIconSize)}${SmallArrowDownIcon}
-
- `}
- >
- this._setBorderRadius(size)}
- .onPopperCose=${() => this._cornersPanelRef.value?.hide()}
- >
-
-
- `,
-
- onlyOne && this._advancedVisibilityEnabled
- ? html`
-