mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-30 00:29:46 +08:00
feat(editor): brush gfx package (#11131)
This commit is contained in:
-80
@@ -1,80 +0,0 @@
|
||||
import { DefaultTheme, type LineWidth } from '@blocksuite/affine-model';
|
||||
import {
|
||||
EditPropsStore,
|
||||
FeatureFlagService,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
|
||||
SignalWatcher(LitElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
menu-divider {
|
||||
height: 24px;
|
||||
margin: 0 9px;
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _props$ = computed(() => {
|
||||
const { color, lineWidth } =
|
||||
this.edgeless.std.get(EditPropsStore).lastProps$.value.brush;
|
||||
return {
|
||||
color,
|
||||
lineWidth,
|
||||
};
|
||||
});
|
||||
|
||||
private readonly _theme$ = computed(() => {
|
||||
return this.edgeless.std.get(ThemeProvider).theme$.value;
|
||||
});
|
||||
|
||||
type: GfxToolsFullOptionValue['type'] = 'brush';
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<edgeless-slide-menu>
|
||||
<div class="menu-content">
|
||||
<edgeless-line-width-panel
|
||||
.selectedSize=${this._props$.value.lineWidth}
|
||||
@select=${(e: CustomEvent<LineWidth>) =>
|
||||
this.onChange({ lineWidth: e.detail })}
|
||||
>
|
||||
</edgeless-line-width-panel>
|
||||
<menu-divider .vertical=${true}></menu-divider>
|
||||
<edgeless-color-panel
|
||||
class="one-way"
|
||||
.value=${this._props$.value.color}
|
||||
.theme=${this._theme$.value}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
.hasTransparent=${!this.edgeless.doc
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_color_picker')}
|
||||
@select=${(e: ColorEvent) =>
|
||||
this.onChange({ color: e.detail.value })}
|
||||
></edgeless-color-panel>
|
||||
</div>
|
||||
</edgeless-slide-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onChange!: (props: Record<string, unknown>) => void;
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
import {
|
||||
EditPropsStore,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { EdgelessPenDarkIcon, EdgelessPenLightIcon } from './icons.js';
|
||||
|
||||
export class EdgelessBrushToolButton extends EdgelessToolbarToolMixin(
|
||||
SignalWatcher(LitElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.edgeless-brush-button {
|
||||
height: 100%;
|
||||
}
|
||||
.pen-wrapper {
|
||||
width: 35px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
#edgeless-pen-icon {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
.edgeless-brush-button:hover #edgeless-pen-icon,
|
||||
.pen-wrapper.active #edgeless-pen-icon {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _color$ = computed(() => {
|
||||
const theme = this.edgeless.std.get(ThemeProvider).theme$.value;
|
||||
return this.edgeless.std
|
||||
.get(ThemeProvider)
|
||||
.generateColorProperty(
|
||||
this.edgeless.std.get(EditPropsStore).lastProps$.value.brush.color,
|
||||
undefined,
|
||||
theme
|
||||
);
|
||||
});
|
||||
|
||||
override enableActiveBackground = true;
|
||||
|
||||
override type = 'brush' as const;
|
||||
|
||||
private _toggleBrushMenu() {
|
||||
if (this.tryDisposePopper()) return;
|
||||
!this.active && this.setEdgelessTool(this.type);
|
||||
const menu = this.createPopper('edgeless-brush-menu', this);
|
||||
Object.assign(menu.element, {
|
||||
edgeless: this.edgeless,
|
||||
onChange: (props: Record<string, unknown>) => {
|
||||
this.edgeless.std.get(EditPropsStore).recordLastProps('brush', props);
|
||||
this.setEdgelessTool('brush');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { active } = this;
|
||||
const appTheme = this.edgeless.std.get(ThemeProvider).app$.value;
|
||||
const icon =
|
||||
appTheme === 'dark' ? EdgelessPenDarkIcon : EdgelessPenLightIcon;
|
||||
const color = this._color$.value;
|
||||
|
||||
return html`
|
||||
<edgeless-toolbar-button
|
||||
class="edgeless-brush-button"
|
||||
.tooltip=${this.popper
|
||||
? ''
|
||||
: html`<affine-tooltip-content-with-shortcut
|
||||
data-tip="${'Pen'}"
|
||||
data-shortcut="${'P'}"
|
||||
></affine-tooltip-content-with-shortcut>`}
|
||||
.tooltipOffset=${4}
|
||||
.active=${active}
|
||||
.withHover=${true}
|
||||
@click=${() => this._toggleBrushMenu()}
|
||||
>
|
||||
<div style=${styleMap({ color })} class="pen-wrapper">${icon}</div>
|
||||
</edgeless-toolbar-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const EdgelessPenLightIcon = html`
|
||||
<svg
|
||||
width="36"
|
||||
height="60"
|
||||
viewBox="0 0 36 60"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="edgeless-pen-icon"
|
||||
>
|
||||
<g filter="url(#filter0_d_5310_64454)">
|
||||
<path
|
||||
d="M8 38.8965L12.2828 37.4689V106.538H8V38.8965Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8 38.8965L12.2828 37.4689V106.538H8V38.8965Z"
|
||||
fill="white"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M12.2832 36.993H17.5177V106.538H12.2832V36.993Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M17.5176 36.993H22.7521V106.538H17.5176V36.993Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M17.5176 36.993H22.7521V106.538H17.5176V36.993Z"
|
||||
fill="black"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M22.752 30.9448L27.0347 38.8965V106.538H22.752V30.9448Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M22.752 30.9448L27.0347 38.8965V106.538H22.752V30.9448Z"
|
||||
fill="black"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M16.5909 2.88078C16.8233 1.90625 18.2099 1.90623 18.4423 2.88075L19.896 8.97414L22.2755 18.9483L27.0345 38.8965L23.9871 38.0231C23.1982 37.7969 22.3511 37.9039 21.6431 38.3189L18.023 40.4414C17.7107 40.6245 17.3238 40.6245 17.0115 40.4414L13.0218 38.1023C12.5499 37.8256 11.9851 37.7543 11.4592 37.905L8 38.8965L12.7583 18.9483L15.1374 8.97414L16.5909 2.88078Z"
|
||||
fill="#F1F1F1"
|
||||
/>
|
||||
<path
|
||||
d="M16.5909 2.88078C16.8233 1.90625 18.2099 1.90623 18.4423 2.88075L19.896 8.97414L22.2755 18.9483L27.0345 38.8965L23.9871 38.0231C23.1982 37.7969 22.3511 37.9039 21.6431 38.3189L18.023 40.4414C17.7107 40.6245 17.3238 40.6245 17.0115 40.4414L13.0218 38.1023C12.5499 37.8256 11.9851 37.7543 11.4592 37.905L8 38.8965L12.7583 18.9483L15.1374 8.97414L16.5909 2.88078Z"
|
||||
fill="url(#paint0_linear_5310_64454)"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<g filter="url(#filter1_b_5310_64454)">
|
||||
<path
|
||||
d="M16.7391 2.26209C16.9345 1.44293 18.1 1.44293 18.2954 2.26209L20.3725 10.969H14.6621L16.7391 2.26209Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_5310_64454"
|
||||
x="0"
|
||||
y="-5"
|
||||
width="35.0352"
|
||||
height="124"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="4" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_5310_64454"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5310_64454"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_b_5310_64454"
|
||||
x="12.7587"
|
||||
y="-0.255743"
|
||||
width="9.51686"
|
||||
height="13.1282"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="0.951724" />
|
||||
<feComposite
|
||||
in2="SourceAlpha"
|
||||
operator="in"
|
||||
result="effect1_backgroundBlur_5310_64454"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_backgroundBlur_5310_64454"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_5310_64454"
|
||||
x1="22.1949"
|
||||
y1="19.2552"
|
||||
x2="11.0983"
|
||||
y2="21.5941"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop />
|
||||
<stop offset="0.3125" stop-opacity="0" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
`;
|
||||
export const EdgelessPenDarkIcon = html`<svg
|
||||
width="34"
|
||||
height="60"
|
||||
viewBox="0 0 34 60"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="edgeless-pen-icon"
|
||||
>
|
||||
<g filter="url(#filter0_d_5310_64464)">
|
||||
<path
|
||||
d="M7 38.8965L11.2828 37.4689V106.538H7V38.8965Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M7 38.8965L11.2828 37.4689V106.538H7V38.8965Z"
|
||||
fill="black"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M11.2832 36.993H16.5177V106.538H11.2832V36.993Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M11.2832 36.993H16.5177V106.538H11.2832V36.993Z"
|
||||
fill="black"
|
||||
fill-opacity="0.26"
|
||||
/>
|
||||
<path
|
||||
d="M16.5176 36.993H21.7521V106.538H16.5176V36.993Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M16.5176 36.993H21.7521V106.538H16.5176V36.993Z"
|
||||
fill="black"
|
||||
fill-opacity="0.4"
|
||||
/>
|
||||
<path
|
||||
d="M21.752 30.9448L26.0347 38.8965V106.538H21.752V30.9448Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M21.752 30.9448L26.0347 38.8965V106.538H21.752V30.9448Z"
|
||||
fill="black"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M15.5909 2.88078C15.8233 1.90625 17.2099 1.90623 17.4423 2.88075L18.896 8.97414L21.2755 18.9483L26.0345 38.8965L22.9871 38.0231C22.1982 37.7969 21.3511 37.9039 20.6431 38.3189L17.023 40.4414C16.7107 40.6245 16.3238 40.6245 16.0115 40.4414L12.0218 38.1023C11.5499 37.8256 10.9851 37.7543 10.4592 37.905L7 38.8965L11.7583 18.9483L14.1374 8.97414L15.5909 2.88078Z"
|
||||
fill="#C1C1C1"
|
||||
/>
|
||||
<path
|
||||
d="M15.5909 2.88078C15.8233 1.90625 17.2099 1.90623 17.4423 2.88075L18.896 8.97414L21.2755 18.9483L26.0345 38.8965L22.9871 38.0231C22.1982 37.7969 21.3511 37.9039 20.6431 38.3189L17.023 40.4414C16.7107 40.6245 16.3238 40.6245 16.0115 40.4414L12.0218 38.1023C11.5499 37.8256 10.9851 37.7543 10.4592 37.905L7 38.8965L11.7583 18.9483L14.1374 8.97414L15.5909 2.88078Z"
|
||||
fill="url(#paint0_linear_5310_64464)"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<g filter="url(#filter1_b_5310_64464)">
|
||||
<path
|
||||
d="M15.7391 2.26209C15.9345 1.44293 17.1 1.44293 17.2954 2.26209L19.3725 10.969H13.6621L15.7391 2.26209Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M15.7391 2.26209C15.9345 1.44293 17.1 1.44293 17.2954 2.26209L19.3725 10.969H13.6621L15.7391 2.26209Z"
|
||||
fill="black"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_5310_64464"
|
||||
x="0"
|
||||
y="-6"
|
||||
width="33.0352"
|
||||
height="122"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2" />
|
||||
<feGaussianBlur stdDeviation="3.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.78 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_5310_64464"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5310_64464"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_b_5310_64464"
|
||||
x="11.7587"
|
||||
y="-0.255743"
|
||||
width="9.51686"
|
||||
height="13.1282"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="0.951724" />
|
||||
<feComposite
|
||||
in2="SourceAlpha"
|
||||
operator="in"
|
||||
result="effect1_backgroundBlur_5310_64464"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_backgroundBlur_5310_64464"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_5310_64464"
|
||||
x1="21.1949"
|
||||
y1="19.2552"
|
||||
x2="11.5553"
|
||||
y2="21.8444"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop />
|
||||
<stop offset="0.302413" stop-opacity="0" />
|
||||
<stop offset="0.557292" stop-opacity="0" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>`;
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
|
||||
import { EdgelessEraserDarkIcon, EdgelessEraserLightIcon } from './icons.js';
|
||||
|
||||
export class EdgelessEraserToolButton extends EdgelessToolbarToolMixin(
|
||||
LitElement
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.eraser-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
position: relative;
|
||||
width: 49px;
|
||||
height: 64px;
|
||||
}
|
||||
#edgeless-eraser-icon {
|
||||
transition: transform 0.3s ease-in-out;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
.eraser-button:hover #edgeless-eraser-icon,
|
||||
.eraser-button.active #edgeless-eraser-icon {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
override enableActiveBackground = true;
|
||||
|
||||
override type: GfxToolsFullOptionValue['type'] = 'eraser';
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
this.edgeless.bindHotKey(
|
||||
{
|
||||
Escape: () => {
|
||||
if (this.edgelessTool.type === 'eraser') {
|
||||
this.setEdgelessTool({ type: 'default' });
|
||||
}
|
||||
},
|
||||
},
|
||||
{ global: true }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const type = this.edgelessTool?.type;
|
||||
const appTheme = this.edgeless.std.get(ThemeProvider).app$.value;
|
||||
const icon =
|
||||
appTheme === 'dark' ? EdgelessEraserDarkIcon : EdgelessEraserLightIcon;
|
||||
|
||||
return html`
|
||||
<edgeless-toolbar-button
|
||||
class="edgeless-eraser-button"
|
||||
.tooltip=${html`<affine-tooltip-content-with-shortcut
|
||||
data-tip="${'Eraser'}"
|
||||
data-shortcut="${'E'}"
|
||||
></affine-tooltip-content-with-shortcut>`}
|
||||
.tooltipOffset=${4}
|
||||
.active=${type === 'eraser'}
|
||||
@click=${() => this.setEdgelessTool({ type: 'eraser' })}
|
||||
>
|
||||
<div class="eraser-button">${icon}</div>
|
||||
</edgeless-toolbar-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const EdgelessEraserLightIcon = html`<svg
|
||||
width="44"
|
||||
height="49"
|
||||
viewBox="0 0 44 49"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="edgeless-eraser-icon"
|
||||
>
|
||||
<g filter="url(#filter0_d_5310_64451)">
|
||||
<rect x="6" y="2" width="32" height="59" rx="5.75" fill="#F1F1F1" />
|
||||
<rect x="6.5" y="2.5" width="31" height="58" rx="5.25" stroke="#E3E2E4" />
|
||||
</g>
|
||||
<g filter="url(#filter1_f_5310_64451)">
|
||||
<rect
|
||||
x="36.2002"
|
||||
y="4.44995"
|
||||
width="18.4"
|
||||
height="3.45"
|
||||
rx="1.725"
|
||||
transform="rotate(90 36.2002 4.44995)"
|
||||
fill="white"
|
||||
fill-opacity="0.78"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_5310_64451)">
|
||||
<path
|
||||
d="M4 60H40V22C40 18.6863 37.3137 16 34 16H10C6.68629 16 4 18.6863 4 22V60Z"
|
||||
fill="#343434"
|
||||
fill-opacity="0.22"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
fill="#173654"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
fill="url(#paint0_linear_5310_64451)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
stroke="#E7E7E7"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
fill="#1E96EB"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
fill="url(#paint1_linear_5310_64451)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
stroke="#E7E7E7"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
fill="#EFFAFF"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
fill="url(#paint2_linear_5310_64451)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
stroke="#E7E7E7"
|
||||
/>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_5310_64451"
|
||||
x="2"
|
||||
y="2"
|
||||
width="40"
|
||||
height="67"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.258824 0 0 0 0 0.254902 0 0 0 0 0.286275 0 0 0 0.18 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_5310_64451"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5310_64451"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_f_5310_64451"
|
||||
x="30.45"
|
||||
y="2.14995"
|
||||
width="8.0502"
|
||||
height="23"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="1.15"
|
||||
result="effect1_foregroundBlur_5310_64451"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_f_5310_64451"
|
||||
x="0"
|
||||
y="12"
|
||||
width="44"
|
||||
height="52"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="2"
|
||||
result="effect1_foregroundBlur_5310_64451"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_5310_64451"
|
||||
x1="11"
|
||||
y1="16"
|
||||
x2="11"
|
||||
y2="62"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_5310_64451"
|
||||
x1="33"
|
||||
y1="16"
|
||||
x2="33"
|
||||
y2="62"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_5310_64451"
|
||||
x1="6"
|
||||
y1="0"
|
||||
x2="6"
|
||||
y2="46"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FFF8F8" stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>`;
|
||||
export const EdgelessEraserDarkIcon = html`<svg
|
||||
width="44"
|
||||
height="49"
|
||||
viewBox="0 0 44 49"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="edgeless-eraser-icon"
|
||||
>
|
||||
<g filter="url(#filter0_d_5310_64471)">
|
||||
<path
|
||||
d="M6 7.75C6 4.57436 8.57436 2 11.75 2H32.25C35.4256 2 38 4.57436 38 7.75V61H6V7.75Z"
|
||||
fill="#C1C1C1"
|
||||
/>
|
||||
<path
|
||||
d="M6.5 7.75C6.5 4.85051 8.85051 2.5 11.75 2.5H32.25C35.1495 2.5 37.5 4.85051 37.5 7.75V60.5H6.5V7.75Z"
|
||||
stroke="#DDDDDD"
|
||||
/>
|
||||
<path
|
||||
d="M6.5 7.75C6.5 4.85051 8.85051 2.5 11.75 2.5H32.25C35.1495 2.5 37.5 4.85051 37.5 7.75V60.5H6.5V7.75Z"
|
||||
stroke="black"
|
||||
stroke-opacity="0.3"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_5310_64471)">
|
||||
<rect
|
||||
x="36.2002"
|
||||
y="4.44995"
|
||||
width="18.4"
|
||||
height="3.45"
|
||||
rx="1.725"
|
||||
transform="rotate(90 36.2002 4.44995)"
|
||||
fill="white"
|
||||
fill-opacity="0.38"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_5310_64471)">
|
||||
<path
|
||||
d="M4 62H40V23C40 19.6863 37.3137 17 34 17H10C6.68629 17 4 19.6863 4 23V62Z"
|
||||
fill="black"
|
||||
fill-opacity="0.44"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
fill="#0D2338"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
fill="url(#paint0_linear_5310_64471)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
stroke="#D3D3D3"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 16.5H11H9.37046C8.79521 16.5 8.29403 16.8921 8.15566 17.4505L8.14174 17.5067C7.93847 18.3269 7.27583 18.9534 6.44553 19.1105C5.89708 19.2142 5.5 19.6934 5.5 20.2516V21.75V27.5V39V61.5H16.5V16.5Z"
|
||||
stroke="black"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
fill="#1A7CC1"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
fill="url(#paint1_linear_5310_64471)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
stroke="#D3D3D3"
|
||||
/>
|
||||
<path
|
||||
d="M27.5 16.5H33H34.6295C35.2048 16.5 35.706 16.8921 35.8443 17.4505L35.8583 17.5067C36.0615 18.3269 36.7242 18.9534 37.5545 19.1105C38.1029 19.2142 38.5 19.6934 38.5 20.2516V21.75V27.5V39V61.5H27.5V16.5Z"
|
||||
stroke="black"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
fill="#D0DFE5"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
fill="url(#paint2_linear_5310_64471)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
stroke="#D3D3D3"
|
||||
/>
|
||||
<rect
|
||||
x="-0.5"
|
||||
y="0.5"
|
||||
width="11"
|
||||
height="45"
|
||||
transform="matrix(-1 0 0 1 27 16)"
|
||||
stroke="black"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_5310_64471"
|
||||
x="2"
|
||||
y="0"
|
||||
width="40"
|
||||
height="67"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.195003 0 0 0 0 0.0133398 0 0 0 0 0.0133398 0 0 0 0.66 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_5310_64471"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5310_64471"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_f_5310_64471"
|
||||
x="30.45"
|
||||
y="2.14995"
|
||||
width="8.0502"
|
||||
height="23"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="1.15"
|
||||
result="effect1_foregroundBlur_5310_64471"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_f_5310_64471"
|
||||
x="0"
|
||||
y="13"
|
||||
width="44"
|
||||
height="53"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="2"
|
||||
result="effect1_foregroundBlur_5310_64471"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_5310_64471"
|
||||
x1="11"
|
||||
y1="16"
|
||||
x2="11"
|
||||
y2="62"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_5310_64471"
|
||||
x1="33"
|
||||
y1="16"
|
||||
x2="33"
|
||||
y2="62"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_5310_64471"
|
||||
x1="6"
|
||||
y1="0"
|
||||
x2="6"
|
||||
y2="46"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FFF8F8" stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>`;
|
||||
@@ -1,5 +1,7 @@
|
||||
import { frameQuickTool } from '@blocksuite/affine-block-frame';
|
||||
import { penSeniorTool } from '@blocksuite/affine-gfx-brush';
|
||||
import { connectorQuickTool } from '@blocksuite/affine-gfx-connector';
|
||||
import { mindMapSeniorTool } from '@blocksuite/affine-gfx-mindmap';
|
||||
import { noteSeniorTool } from '@blocksuite/affine-gfx-note';
|
||||
import { shapeSeniorTool } from '@blocksuite/affine-gfx-shape';
|
||||
import {
|
||||
@@ -28,34 +30,6 @@ const linkQuickTool = QuickToolExtension('link', ({ block, gfx }) => {
|
||||
};
|
||||
});
|
||||
|
||||
const penSeniorTool = SeniorToolExtension('pen', ({ block }) => {
|
||||
return {
|
||||
name: 'Pen',
|
||||
content: html`<div class="brush-and-eraser">
|
||||
<edgeless-brush-tool-button
|
||||
.edgeless=${block}
|
||||
></edgeless-brush-tool-button>
|
||||
|
||||
<edgeless-eraser-tool-button
|
||||
.edgeless=${block}
|
||||
></edgeless-eraser-tool-button>
|
||||
</div> `,
|
||||
};
|
||||
});
|
||||
|
||||
const mindMapSeniorTool = SeniorToolExtension(
|
||||
'mindMap',
|
||||
({ block, toolbarContainer }) => {
|
||||
return {
|
||||
name: 'Mind Map',
|
||||
content: html`<edgeless-mindmap-tool-button
|
||||
.edgeless=${block}
|
||||
.toolbarContainer=${toolbarContainer}
|
||||
></edgeless-mindmap-tool-button>`,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const templateSeniorTool = SeniorToolExtension('template', ({ block }) => {
|
||||
return {
|
||||
name: 'Template',
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
packColor,
|
||||
type PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
BrushElementModel,
|
||||
DefaultTheme,
|
||||
LineWidth,
|
||||
resolveColor,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { type ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
getMostCommonResolvedValue,
|
||||
getMostCommonValue,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { html } from 'lit';
|
||||
|
||||
export const builtinBrushToolbarConfig = {
|
||||
actions: [
|
||||
{
|
||||
id: 'a.line-width',
|
||||
content(ctx) {
|
||||
const models = ctx.getSurfaceModelsByType(BrushElementModel);
|
||||
if (!models.length) return null;
|
||||
|
||||
const lineWidth =
|
||||
getMostCommonValue(models, 'lineWidth') ?? LineWidth.Four;
|
||||
const onPick = (e: CustomEvent<LineWidth>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const lineWidth = e.detail;
|
||||
|
||||
for (const model of models) {
|
||||
ctx.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.updateElement(model.id, { lineWidth });
|
||||
}
|
||||
};
|
||||
|
||||
return html`
|
||||
<edgeless-line-width-panel
|
||||
.selectedSize=${lineWidth}
|
||||
@select=${onPick}
|
||||
>
|
||||
</edgeless-line-width-panel>
|
||||
`;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'b.color-picker',
|
||||
content(ctx) {
|
||||
const models = ctx.getSurfaceModelsByType(BrushElementModel);
|
||||
if (!models.length) return null;
|
||||
|
||||
const enableCustomColor = ctx.features.getFlag('enable_color_picker');
|
||||
const theme = ctx.theme.edgeless$.value;
|
||||
|
||||
const field = 'color';
|
||||
const firstModel = models[0];
|
||||
const originalColor = firstModel[field];
|
||||
const color =
|
||||
getMostCommonResolvedValue(models, field, color =>
|
||||
resolveColor(color, theme)
|
||||
) ?? resolveColor(DefaultTheme.black, theme);
|
||||
const onPick = (e: PickColorEvent) => {
|
||||
if (e.type === 'pick') {
|
||||
const color = e.detail.value;
|
||||
for (const model of models) {
|
||||
const props = packColor(field, color);
|
||||
ctx.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.updateElement(model.id, props);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
model[e.type === 'start' ? 'stash' : 'pop'](field);
|
||||
}
|
||||
};
|
||||
|
||||
return html`
|
||||
<edgeless-color-picker-button
|
||||
class="color"
|
||||
.label="${'Color'}"
|
||||
.pick=${onPick}
|
||||
.color=${color}
|
||||
.theme=${theme}
|
||||
.originalColor=${originalColor}
|
||||
.enableCustomColor=${enableCustomColor}
|
||||
>
|
||||
</edgeless-color-picker-button>
|
||||
`;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
when: ctx => ctx.getSurfaceModelsByType(BrushElementModel).length > 0,
|
||||
} as const satisfies ToolbarModuleConfig;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { edgelessTextToolbarExtension } from '@blocksuite/affine-block-edgeless-text';
|
||||
import { frameToolbarExtension } from '@blocksuite/affine-block-frame';
|
||||
import { brushToolbarExtension } from '@blocksuite/affine-gfx-brush';
|
||||
import { connectorToolbarExtension } from '@blocksuite/affine-gfx-connector';
|
||||
import { groupToolbarExtension } from '@blocksuite/affine-gfx-group';
|
||||
import { mindmapToolbarExtension } from '@blocksuite/affine-gfx-mindmap';
|
||||
@@ -9,7 +10,6 @@ import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { builtinBrushToolbarConfig } from './brush';
|
||||
import { builtinLockedToolbarConfig, builtinMiscToolbarConfig } from './misc';
|
||||
|
||||
export const EdgelessElementToolbarExtension: ExtensionType[] = [
|
||||
@@ -17,10 +17,7 @@ export const EdgelessElementToolbarExtension: ExtensionType[] = [
|
||||
|
||||
groupToolbarExtension,
|
||||
|
||||
ToolbarModuleExtension({
|
||||
id: BlockFlavourIdentifier('affine:surface:brush'),
|
||||
config: builtinBrushToolbarConfig,
|
||||
}),
|
||||
brushToolbarExtension,
|
||||
|
||||
connectorToolbarExtension,
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
PresentTool,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
|
||||
import { BrushTool, EraserTool } from '@blocksuite/affine-gfx-brush';
|
||||
import {
|
||||
ConnectorFilter,
|
||||
ConnectorTool,
|
||||
@@ -24,10 +25,8 @@ import type { ExtensionType } from '@blocksuite/store';
|
||||
import { EdgelessElementToolbarExtension } from './configs/toolbar';
|
||||
import { EdgelessRootBlockSpec } from './edgeless-root-spec.js';
|
||||
import { SnapExtension } from './element-transform/snap-manager.js';
|
||||
import { BrushTool } from './gfx-tool/brush-tool.js';
|
||||
import { DefaultTool } from './gfx-tool/default-tool.js';
|
||||
import { EmptyTool } from './gfx-tool/empty-tool.js';
|
||||
import { EraserTool } from './gfx-tool/eraser-tool.js';
|
||||
import { LassoTool } from './gfx-tool/lasso-tool.js';
|
||||
import { PanTool } from './gfx-tool/pan-tool.js';
|
||||
import { TemplateTool } from './gfx-tool/template-tool.js';
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import { CanvasElementType } from '@blocksuite/affine-block-surface';
|
||||
import type { BrushElementModel } from '@blocksuite/affine-model';
|
||||
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
|
||||
import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import { BaseTool } from '@blocksuite/block-std/gfx';
|
||||
import type { IVec } from '@blocksuite/global/gfx';
|
||||
|
||||
export class BrushTool extends BaseTool {
|
||||
static BRUSH_POP_GAP = 20;
|
||||
|
||||
static override toolName: string = 'brush';
|
||||
|
||||
private _draggingElement: BrushElementModel | null = null;
|
||||
|
||||
private _draggingElementId: string | null = null;
|
||||
|
||||
private _lastPoint: IVec | null = null;
|
||||
|
||||
private _lastPopLength = 0;
|
||||
|
||||
private readonly _pressureSupportedPointerIds = new Set<number>();
|
||||
|
||||
private _straightLineType: 'horizontal' | 'vertical' | null = null;
|
||||
|
||||
protected _draggingPathPoints: number[][] | null = null;
|
||||
|
||||
protected _draggingPathPressures: number[] | null = null;
|
||||
|
||||
private _getStraightLineType(currentPoint: IVec) {
|
||||
const lastPoint = this._lastPoint;
|
||||
if (!lastPoint) return null;
|
||||
|
||||
// check angle to determine if the line is horizontal or vertical
|
||||
const dx = currentPoint[0] - lastPoint[0];
|
||||
const dy = currentPoint[1] - lastPoint[1];
|
||||
const absAngleRadius = Math.abs(Math.atan2(dy, dx));
|
||||
return absAngleRadius < Math.PI / 4 || absAngleRadius > 3 * (Math.PI / 4)
|
||||
? 'horizontal'
|
||||
: 'vertical';
|
||||
}
|
||||
|
||||
private _tryGetPressurePoints(e: PointerEventState): number[][] {
|
||||
if (!this._draggingPathPressures) {
|
||||
return [];
|
||||
}
|
||||
const pressures = [...this._draggingPathPressures, e.pressure];
|
||||
this._draggingPathPressures = pressures;
|
||||
|
||||
// we do not use the `e.raw.pointerType` to detect because it is not reliable,
|
||||
// such as some digital pens do not support pressure even thought the `e.raw.pointerType` is equal to `'pen'`
|
||||
const pointerId = e.raw.pointerId;
|
||||
const pressureChanged = pressures.some(
|
||||
pressure => pressure !== pressures[0]
|
||||
);
|
||||
|
||||
if (pressureChanged) {
|
||||
this._pressureSupportedPointerIds.add(pointerId);
|
||||
}
|
||||
|
||||
const points = this._draggingPathPoints;
|
||||
if (!points) {
|
||||
return [];
|
||||
}
|
||||
if (this._pressureSupportedPointerIds.has(pointerId)) {
|
||||
return points.map(([x, y], i) => [x, y, pressures[i]]);
|
||||
} else {
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
override dragEnd() {
|
||||
if (this._draggingElement) {
|
||||
const { _draggingElement } = this;
|
||||
this.doc.withoutTransact(() => {
|
||||
_draggingElement.pop('points');
|
||||
_draggingElement.pop('xywh');
|
||||
});
|
||||
}
|
||||
this._draggingElement = null;
|
||||
this._draggingElementId = null;
|
||||
this._draggingPathPoints = null;
|
||||
this._draggingPathPressures = null;
|
||||
this._lastPoint = null;
|
||||
this._straightLineType = null;
|
||||
this.doc.captureSync();
|
||||
}
|
||||
|
||||
override dragMove(e: PointerEventState) {
|
||||
if (
|
||||
!this._draggingElementId ||
|
||||
!this._draggingElement ||
|
||||
!this.gfx.surface ||
|
||||
!this._draggingPathPoints
|
||||
)
|
||||
return;
|
||||
|
||||
let pointX = e.point.x;
|
||||
let pointY = e.point.y;
|
||||
const holdingShiftKey = e.keys.shift || this.gfx.keyboard.shiftKey$.peek();
|
||||
if (holdingShiftKey) {
|
||||
if (!this._straightLineType) {
|
||||
this._straightLineType = this._getStraightLineType([pointX, pointY]);
|
||||
}
|
||||
|
||||
if (this._straightLineType === 'horizontal') {
|
||||
pointY = this._lastPoint?.[1] ?? pointY;
|
||||
} else if (this._straightLineType === 'vertical') {
|
||||
pointX = this._lastPoint?.[0] ?? pointX;
|
||||
}
|
||||
} else if (this._straightLineType) {
|
||||
this._straightLineType = null;
|
||||
}
|
||||
|
||||
const [modelX, modelY] = this.gfx.viewport.toModelCoord(pointX, pointY);
|
||||
|
||||
const points = [...this._draggingPathPoints, [modelX, modelY]];
|
||||
|
||||
this._lastPoint = [pointX, pointY];
|
||||
this._draggingPathPoints = points;
|
||||
|
||||
this.gfx.updateElement(this._draggingElement!, {
|
||||
points: this._tryGetPressurePoints(e),
|
||||
});
|
||||
|
||||
if (
|
||||
this._lastPopLength + BrushTool.BRUSH_POP_GAP <
|
||||
this._draggingElement!.points.length
|
||||
) {
|
||||
this._lastPopLength = this._draggingElement!.points.length;
|
||||
this.doc.withoutTransact(() => {
|
||||
this._draggingElement!.pop('points');
|
||||
this._draggingElement!.pop('xywh');
|
||||
});
|
||||
|
||||
this._draggingElement!.stash('points');
|
||||
this._draggingElement!.stash('xywh');
|
||||
}
|
||||
}
|
||||
|
||||
override dragStart(e: PointerEventState) {
|
||||
if (!this.gfx.surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.doc.captureSync();
|
||||
|
||||
const { viewport } = this.gfx;
|
||||
|
||||
// create a shape block when drag start
|
||||
const [modelX, modelY] = viewport.toModelCoord(e.point.x, e.point.y);
|
||||
const points = [[modelX, modelY]];
|
||||
const id = this.gfx.surface.addElement({
|
||||
type: CanvasElementType.BRUSH,
|
||||
points,
|
||||
});
|
||||
|
||||
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
|
||||
control: 'canvas:draw',
|
||||
page: 'whiteboard editor',
|
||||
module: 'toolbar',
|
||||
segment: 'toolbar',
|
||||
type: CanvasElementType.BRUSH,
|
||||
});
|
||||
|
||||
const element = this.gfx.getElementById(id) as BrushElementModel;
|
||||
|
||||
element.stash('points');
|
||||
element.stash('xywh');
|
||||
|
||||
this._lastPoint = [e.point.x, e.point.y];
|
||||
this._draggingElementId = id;
|
||||
this._draggingElement = element;
|
||||
this._draggingPathPoints = points;
|
||||
this._draggingPathPressures = [e.pressure];
|
||||
this._lastPopLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@blocksuite/block-std/gfx' {
|
||||
interface GfxToolsMap {
|
||||
brush: BrushTool;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
import {
|
||||
EdgelessCRUDIdentifier,
|
||||
Overlay,
|
||||
type SurfaceBlockComponent,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { isTopLevelBlock } from '@blocksuite/affine-shared/utils';
|
||||
import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import { BaseTool, type GfxModel } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
Bound,
|
||||
getStroke,
|
||||
getSvgPathFromStroke,
|
||||
type IVec,
|
||||
linePolygonIntersects,
|
||||
} from '@blocksuite/global/gfx';
|
||||
|
||||
class EraserOverlay extends Overlay {
|
||||
d = '';
|
||||
|
||||
override render(ctx: CanvasRenderingContext2D): void {
|
||||
ctx.globalAlpha = 0.33;
|
||||
const path = new Path2D(this.d);
|
||||
ctx.fillStyle = '#aaa';
|
||||
ctx.fill(path);
|
||||
}
|
||||
}
|
||||
|
||||
export class EraserTool extends BaseTool {
|
||||
static override toolName = 'eraser';
|
||||
|
||||
private _erasable = new Set<GfxModel>();
|
||||
|
||||
private _eraserPoints: IVec[] = [];
|
||||
|
||||
private readonly _eraseTargets = new Set<GfxModel>();
|
||||
|
||||
private readonly _loop = () => {
|
||||
const now = Date.now();
|
||||
const elapsed = now - this._timestamp;
|
||||
|
||||
let didUpdate = false;
|
||||
|
||||
if (this._prevEraserPoint !== this._prevPoint) {
|
||||
didUpdate = true;
|
||||
this._eraserPoints.push(this._prevPoint);
|
||||
this._prevEraserPoint = this._prevPoint;
|
||||
}
|
||||
if (elapsed > 32 && this._eraserPoints.length > 1) {
|
||||
didUpdate = true;
|
||||
this._eraserPoints.splice(0, Math.ceil(this._eraserPoints.length * 0.1));
|
||||
this._timestamp = now;
|
||||
}
|
||||
if (didUpdate) {
|
||||
const zoom = this.gfx.viewport.zoom;
|
||||
const d = getSvgPathFromStroke(
|
||||
getStroke(this._eraserPoints, {
|
||||
size: 16 / zoom,
|
||||
start: { taper: true },
|
||||
})
|
||||
);
|
||||
this._overlay.d = d;
|
||||
(this.gfx.surfaceComponent as SurfaceBlockComponent)?.refresh();
|
||||
}
|
||||
this._timer = requestAnimationFrame(this._loop);
|
||||
};
|
||||
|
||||
private readonly _overlay = new EraserOverlay(this.gfx);
|
||||
|
||||
private _prevEraserPoint: IVec = [0, 0];
|
||||
|
||||
private _prevPoint: IVec = [0, 0];
|
||||
|
||||
private _timer = 0;
|
||||
|
||||
private _timestamp = 0;
|
||||
|
||||
private _reset() {
|
||||
cancelAnimationFrame(this._timer);
|
||||
|
||||
if (!this.gfx.surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
(
|
||||
this.gfx.surfaceComponent as SurfaceBlockComponent
|
||||
)?.renderer.removeOverlay(this._overlay);
|
||||
this._erasable.clear();
|
||||
this._eraseTargets.clear();
|
||||
}
|
||||
|
||||
override activate(): void {
|
||||
this._eraseTargets.forEach(erasable => {
|
||||
if (isTopLevelBlock(erasable)) {
|
||||
const ele = this.std.view.getBlock(erasable.id);
|
||||
ele && ((ele as HTMLElement).style.opacity = '1');
|
||||
} else {
|
||||
erasable.opacity = 1;
|
||||
}
|
||||
});
|
||||
this._reset();
|
||||
}
|
||||
|
||||
override dragEnd(_: PointerEventState): void {
|
||||
this.gfx.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.deleteElements(Array.from(this._eraseTargets));
|
||||
this._reset();
|
||||
this.doc.captureSync();
|
||||
}
|
||||
|
||||
override dragMove(e: PointerEventState): void {
|
||||
const currentPoint = this.gfx.viewport.toModelCoord(e.point.x, e.point.y);
|
||||
this._erasable.forEach(erasable => {
|
||||
if (erasable.isLocked()) return;
|
||||
if (this._eraseTargets.has(erasable)) return;
|
||||
if (isTopLevelBlock(erasable)) {
|
||||
const bound = Bound.deserialize(erasable.xywh);
|
||||
if (
|
||||
linePolygonIntersects(this._prevPoint, currentPoint, bound.points)
|
||||
) {
|
||||
this._eraseTargets.add(erasable);
|
||||
const ele = this.std.view.getBlock(erasable.id);
|
||||
ele && ((ele as HTMLElement).style.opacity = '0.3');
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
erasable.getLineIntersections(
|
||||
this._prevPoint as IVec,
|
||||
currentPoint as IVec
|
||||
)
|
||||
) {
|
||||
this._eraseTargets.add(erasable);
|
||||
erasable.opacity = 0.3;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._prevPoint = currentPoint;
|
||||
}
|
||||
|
||||
override dragStart(e: PointerEventState): void {
|
||||
this.doc.captureSync();
|
||||
|
||||
const { point } = e;
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(point.x, point.y);
|
||||
this._eraserPoints = [[x, y]];
|
||||
this._prevPoint = [x, y];
|
||||
this._erasable = new Set([
|
||||
...this.gfx.layer.canvasElements,
|
||||
...this.gfx.layer.blocks,
|
||||
]);
|
||||
this._loop();
|
||||
(this.gfx.surfaceComponent as SurfaceBlockComponent)?.renderer.addOverlay(
|
||||
this._overlay
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@blocksuite/block-std/gfx' {
|
||||
interface GfxToolsMap {
|
||||
eraser: EraserTool;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
export { BrushTool } from './brush-tool.js';
|
||||
export { DefaultTool } from './default-tool.js';
|
||||
export { EmptyTool } from './empty-tool.js';
|
||||
export { EraserTool } from './eraser-tool.js';
|
||||
export { LassoTool, type LassoToolOption } from './lasso-tool.js';
|
||||
export { PanTool, type PanToolOption } from './pan-tool.js';
|
||||
export { TemplateTool } from './template-tool.js';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { effects as gfxBrushEffects } from '@blocksuite/affine-gfx-brush/effects';
|
||||
import { effects as gfxConnectorEffects } from '@blocksuite/affine-gfx-connector/effects';
|
||||
import { effects as gfxGroupEffects } from '@blocksuite/affine-gfx-group/effects';
|
||||
import { effects as gfxMindmapEffects } from '@blocksuite/affine-gfx-mindmap/effects';
|
||||
@@ -20,12 +21,9 @@ import {
|
||||
EDGELESS_SELECTED_RECT_WIDGET,
|
||||
EdgelessSelectedRectWidget,
|
||||
} from './edgeless/components/rects/edgeless-selected-rect.js';
|
||||
import { EdgelessBrushMenu } from './edgeless/components/toolbar/brush/brush-menu.js';
|
||||
import { EdgelessBrushToolButton } from './edgeless/components/toolbar/brush/brush-tool-button.js';
|
||||
import { EdgelessSlideMenu } from './edgeless/components/toolbar/common/slide-menu.js';
|
||||
import { ToolbarArrowUpIcon } from './edgeless/components/toolbar/common/toolbar-arrow-up-icon.js';
|
||||
import { EdgelessDefaultToolButton } from './edgeless/components/toolbar/default/default-tool-button.js';
|
||||
import { EdgelessEraserToolButton } from './edgeless/components/toolbar/eraser/eraser-tool-button.js';
|
||||
import { EdgelessLassoToolButton } from './edgeless/components/toolbar/lasso/lasso-tool-button.js';
|
||||
import { EdgelessLinkToolButton } from './edgeless/components/toolbar/link/link-tool-button.js';
|
||||
import { OverlayScrollbar } from './edgeless/components/toolbar/template/overlay-scrollbar.js';
|
||||
@@ -101,6 +99,7 @@ function registerGfxEffects() {
|
||||
gfxConnectorEffects();
|
||||
gfxMindmapEffects();
|
||||
gfxGroupEffects();
|
||||
gfxBrushEffects();
|
||||
}
|
||||
|
||||
function registerWidgets() {
|
||||
@@ -123,21 +122,15 @@ function registerWidgets() {
|
||||
|
||||
function registerEdgelessToolbarComponents() {
|
||||
// Tool buttons
|
||||
customElements.define('edgeless-brush-tool-button', EdgelessBrushToolButton);
|
||||
customElements.define(
|
||||
'edgeless-default-tool-button',
|
||||
EdgelessDefaultToolButton
|
||||
);
|
||||
customElements.define(
|
||||
'edgeless-eraser-tool-button',
|
||||
EdgelessEraserToolButton
|
||||
);
|
||||
customElements.define('edgeless-link-tool-button', EdgelessLinkToolButton);
|
||||
customElements.define('edgeless-lasso-tool-button', EdgelessLassoToolButton);
|
||||
customElements.define('edgeless-template-button', EdgelessTemplateButton);
|
||||
|
||||
// Menus
|
||||
customElements.define('edgeless-brush-menu', EdgelessBrushMenu);
|
||||
customElements.define('edgeless-slide-menu', EdgelessSlideMenu);
|
||||
|
||||
// Toolbar components
|
||||
@@ -196,12 +189,9 @@ declare global {
|
||||
'edgeless-navigator-black-background': EdgelessNavigatorBlackBackgroundWidget;
|
||||
'edgeless-dragging-area-rect': EdgelessDraggingAreaRectWidget;
|
||||
'edgeless-selected-rect': EdgelessSelectedRectWidget;
|
||||
'edgeless-brush-menu': EdgelessBrushMenu;
|
||||
'edgeless-brush-tool-button': EdgelessBrushToolButton;
|
||||
'edgeless-slide-menu': EdgelessSlideMenu;
|
||||
'toolbar-arrow-up-icon': ToolbarArrowUpIcon;
|
||||
'edgeless-default-tool-button': EdgelessDefaultToolButton;
|
||||
'edgeless-eraser-tool-button': EdgelessEraserToolButton;
|
||||
'edgeless-lasso-tool-button': EdgelessLassoToolButton;
|
||||
'edgeless-link-tool-button': EdgelessLinkToolButton;
|
||||
'overlay-scrollbar': OverlayScrollbar;
|
||||
|
||||
Reference in New Issue
Block a user