mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-21 08:17:10 +08:00
feat(editor): add highlighter (#10573)
Closes: [BS-2909](https://linear.app/affine-design/issue/BS-2909/新增highlighter) ### What's Changed! Currently the highlighter tool is very similar to brush, but for the future, it's a standalone module. * Added `Highlighter` element model * Added `Highlighter` tool * Added `Highlighter` entry to the global toolbar
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { edgelessTextToolbarExtension } from '@blocksuite/affine-block-edgeless-text';
|
||||
import { frameToolbarExtension } from '@blocksuite/affine-block-frame';
|
||||
import { brushToolbarExtension } from '@blocksuite/affine-gfx-brush';
|
||||
import {
|
||||
brushToolbarExtension,
|
||||
highlighterToolbarExtension,
|
||||
} 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';
|
||||
@@ -19,6 +22,8 @@ export const EdgelessElementToolbarExtension: ExtensionType[] = [
|
||||
|
||||
brushToolbarExtension,
|
||||
|
||||
highlighterToolbarExtension,
|
||||
|
||||
connectorToolbarExtension,
|
||||
|
||||
mindmapToolbarExtension,
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
PresentTool,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
|
||||
import { BrushTool, EraserTool } from '@blocksuite/affine-gfx-brush';
|
||||
import {
|
||||
BrushTool,
|
||||
EraserTool,
|
||||
HighlighterTool,
|
||||
} from '@blocksuite/affine-gfx-brush';
|
||||
import {
|
||||
ConnectorFilter,
|
||||
ConnectorTool,
|
||||
@@ -45,6 +49,7 @@ export const EdgelessToolExtension: ExtensionType[] = [
|
||||
FrameTool,
|
||||
LassoTool,
|
||||
PresentTool,
|
||||
HighlighterTool,
|
||||
];
|
||||
|
||||
export const EdgelessEditExtensions: ExtensionType[] = [
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
BrushElementModel,
|
||||
ConnectorElementModel,
|
||||
GroupElementModel,
|
||||
HighlighterElementModel,
|
||||
MindmapElementModel,
|
||||
ShapeElementModel,
|
||||
TextElementModel,
|
||||
@@ -16,12 +17,14 @@ export const elementsCtorMap = {
|
||||
brush: BrushElementModel,
|
||||
text: TextElementModel,
|
||||
mindmap: MindmapElementModel,
|
||||
highlighter: HighlighterElementModel,
|
||||
};
|
||||
|
||||
export {
|
||||
BrushElementModel,
|
||||
ConnectorElementModel,
|
||||
GroupElementModel,
|
||||
HighlighterElementModel,
|
||||
MindmapElementModel,
|
||||
ShapeElementModel,
|
||||
SurfaceElementModel,
|
||||
@@ -35,6 +38,7 @@ export enum CanvasElementType {
|
||||
MINDMAP = 'mindmap',
|
||||
SHAPE = 'shape',
|
||||
TEXT = 'text',
|
||||
HIGHLIGHTER = 'highlighter',
|
||||
}
|
||||
|
||||
export type ElementModelMap = {
|
||||
@@ -44,6 +48,7 @@ export type ElementModelMap = {
|
||||
['text']: TextElementModel;
|
||||
['group']: GroupElementModel;
|
||||
['mindmap']: MindmapElementModel;
|
||||
['highlighter']: HighlighterElementModel;
|
||||
};
|
||||
|
||||
export function isCanvasElementType(type: string): type is CanvasElementType {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
DefaultTheme,
|
||||
type HighlighterElementModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
|
||||
import type { CanvasRenderer } from '../../canvas-renderer.js';
|
||||
|
||||
export function highlighter(
|
||||
model: HighlighterElementModel,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
matrix: DOMMatrix,
|
||||
renderer: CanvasRenderer
|
||||
) {
|
||||
const {
|
||||
rotate,
|
||||
deserializedXYWH: [, , w, h],
|
||||
} = model;
|
||||
const cx = w / 2;
|
||||
const cy = h / 2;
|
||||
|
||||
ctx.setTransform(
|
||||
matrix.translateSelf(cx, cy).rotateSelf(rotate).translateSelf(-cx, -cy)
|
||||
);
|
||||
|
||||
const color = renderer.getColorValue(
|
||||
model.color,
|
||||
DefaultTheme.hightlighterColor,
|
||||
true
|
||||
);
|
||||
|
||||
ctx.fillStyle = color;
|
||||
|
||||
ctx.fill(new Path2D(model.commands));
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import type { CanvasRenderer } from '../canvas-renderer.js';
|
||||
import { brush } from './brush/index.js';
|
||||
import { connector } from './connector/index.js';
|
||||
import { group } from './group/index.js';
|
||||
import { highlighter } from './highlighter/index.js';
|
||||
import { mindmap } from './mindmap.js';
|
||||
import { shape } from './shape/index.js';
|
||||
import { text } from './text/index.js';
|
||||
@@ -24,6 +25,7 @@ export type ElementRenderer<
|
||||
|
||||
export const elementRenderers = {
|
||||
brush,
|
||||
highlighter,
|
||||
connector,
|
||||
group,
|
||||
shape,
|
||||
|
||||
@@ -203,6 +203,12 @@ export class EdgelessColorPanel extends LitElement {
|
||||
box-sizing: border-box;
|
||||
background: var(--affine-background-overlay-panel-color);
|
||||
}
|
||||
|
||||
:host(.one-way.small) {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: unset;
|
||||
}
|
||||
`;
|
||||
|
||||
select(palette: Palette) {
|
||||
@@ -221,14 +227,13 @@ export class EdgelessColorPanel extends LitElement {
|
||||
}
|
||||
|
||||
override render() {
|
||||
const resolvedValue = this.resolvedValue;
|
||||
return html`
|
||||
${repeat(
|
||||
this.palettes,
|
||||
palette => palette.key,
|
||||
palette => {
|
||||
const resolvedColor = resolveColor(palette.value, this.theme);
|
||||
const activated = isEqual(resolvedColor, resolvedValue);
|
||||
const activated = isEqual(resolvedColor, this.resolvedValue);
|
||||
return html`<edgeless-color-button
|
||||
class=${classMap({ large: true })}
|
||||
.label=${palette.key}
|
||||
|
||||
@@ -340,3 +340,29 @@ export const calcCustomButtonStyle = (
|
||||
|
||||
return { '--b': b, '--c': c };
|
||||
};
|
||||
|
||||
export const adjustColorAlpha = (color: Color, a: number): Color => {
|
||||
let newColor;
|
||||
if (typeof color === 'object') {
|
||||
if ('normal' in color) {
|
||||
const rgba = parseStringToRgba(color.normal);
|
||||
rgba.a = a;
|
||||
newColor = { normal: rgbaToHex8(rgba) };
|
||||
} else {
|
||||
const newDarkRgba = parseStringToRgba(color.dark);
|
||||
newDarkRgba.a = a;
|
||||
const newLightRgba = parseStringToRgba(color.light);
|
||||
newLightRgba.a = a;
|
||||
newColor = {
|
||||
dark: rgbaToHex8(newDarkRgba),
|
||||
light: rgbaToHex8(newLightRgba),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const rgba = parseStringToRgba(color);
|
||||
rgba.a = a;
|
||||
newColor = rgbaToHex8(rgba);
|
||||
}
|
||||
|
||||
return newColor;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LINE_WIDTHS, LineWidth } from '@blocksuite/affine-model';
|
||||
import { BRUSH_LINE_WIDTHS, LineWidth } from '@blocksuite/affine-model';
|
||||
import { on, once } from '@blocksuite/affine-shared/utils';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
||||
@@ -122,7 +122,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) {
|
||||
this._updateLineWidthPanelByDragHandlePosition(x);
|
||||
};
|
||||
|
||||
private _onSelect(lineWidth: LineWidth) {
|
||||
private _onSelect(lineWidth: number) {
|
||||
// If the selected size is the same as the previous one, do nothing.
|
||||
if (lineWidth === this.selectedSize) return;
|
||||
this.dispatchEvent(
|
||||
@@ -136,7 +136,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) {
|
||||
this.selectedSize = lineWidth;
|
||||
}
|
||||
|
||||
private _updateLineWidthPanel(selectedSize: LineWidth) {
|
||||
private _updateLineWidthPanel(selectedSize: number) {
|
||||
if (!this._lineWidthOverlay) return;
|
||||
const index = this.lineWidths.findIndex(w => w === selectedSize);
|
||||
if (index === -1) return;
|
||||
@@ -221,7 +221,7 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) {
|
||||
itemSize: 16,
|
||||
itemIconSize: 8,
|
||||
dragHandleSize: 14,
|
||||
count: LINE_WIDTHS.length,
|
||||
count: BRUSH_LINE_WIDTHS.length,
|
||||
};
|
||||
|
||||
@property({ attribute: false, type: Boolean })
|
||||
@@ -231,10 +231,10 @@ export class EdgelessLineWidthPanel extends WithDisposable(LitElement) {
|
||||
accessor hasTooltip = true;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor lineWidths: LineWidth[] = LINE_WIDTHS;
|
||||
accessor lineWidths: number[] = BRUSH_LINE_WIDTHS;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor selectedSize: LineWidth = LineWidth.Two;
|
||||
accessor selectedSize: number = LineWidth.Two;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { EdgelessBrushMenu } from './toolbar/components/brush/brush-menu';
|
||||
import { EdgelessBrushToolButton } from './toolbar/components/brush/brush-tool-button';
|
||||
import { EdgelessEraserToolButton } from './toolbar/components/eraser/eraser-tool-button';
|
||||
import { EdgelessPenMenu } from './toolbar/components/pen/pen-menu';
|
||||
import { EdgelessPenToolButton } from './toolbar/components/pen/pen-tool-button';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('edgeless-brush-tool-button', EdgelessBrushToolButton);
|
||||
customElements.define('edgeless-brush-menu', EdgelessBrushMenu);
|
||||
customElements.define(
|
||||
'edgeless-eraser-tool-button',
|
||||
EdgelessEraserToolButton
|
||||
);
|
||||
customElements.define('edgeless-pen-tool-button', EdgelessPenToolButton);
|
||||
customElements.define('edgeless-pen-menu', EdgelessPenMenu);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-brush-tool-button': EdgelessBrushToolButton;
|
||||
'edgeless-brush-menu': EdgelessBrushMenu;
|
||||
'edgeless-pen-menu': EdgelessPenMenu;
|
||||
'edgeless-pen-tool-button': EdgelessPenToolButton;
|
||||
'edgeless-eraser-tool-button': EdgelessEraserToolButton;
|
||||
}
|
||||
}
|
||||
|
||||
183
blocksuite/affine/gfx/brush/src/highlighter-tool.ts
Normal file
183
blocksuite/affine/gfx/brush/src/highlighter-tool.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { CanvasElementType } from '@blocksuite/affine-block-surface';
|
||||
import type { HighlighterElementModel } 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 HighlighterTool extends BaseTool {
|
||||
static HIGHLIGHTER_POP_GAP = 20;
|
||||
|
||||
static override toolName: string = 'highlighter';
|
||||
|
||||
private _draggingElement: HighlighterElementModel | 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 + HighlighterTool.HIGHLIGHTER_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.HIGHLIGHTER,
|
||||
points,
|
||||
});
|
||||
|
||||
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
|
||||
control: 'canvas:draw',
|
||||
page: 'whiteboard editor',
|
||||
module: 'toolbar',
|
||||
segment: 'toolbar',
|
||||
type: CanvasElementType.HIGHLIGHTER,
|
||||
});
|
||||
|
||||
const element = this.gfx.getElementById(id) as HighlighterElementModel;
|
||||
|
||||
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 {
|
||||
highlighter: HighlighterTool;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './brush-tool';
|
||||
export * from './eraser-tool';
|
||||
export * from './toolbar/config';
|
||||
export * from './highlighter-tool';
|
||||
export * from './toolbar/configs';
|
||||
export * from './toolbar/senior-tool';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>`;
|
||||
608
blocksuite/affine/gfx/brush/src/toolbar/components/pen/icons.ts
Normal file
608
blocksuite/affine/gfx/brush/src/toolbar/components/pen/icons.ts
Normal file
@@ -0,0 +1,608 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const EdgelessBrushLightIcon = html`
|
||||
<svg
|
||||
width="36"
|
||||
height="60"
|
||||
viewBox="0 0 36 60"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<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 EdgelessBrushDarkIcon = html`<svg
|
||||
width="34"
|
||||
height="60"
|
||||
viewBox="0 0 34 60"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<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>`;
|
||||
|
||||
export const EdgelessHighlighterDarkIcon = html`
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="29"
|
||||
height="63"
|
||||
viewBox="0 0 29 63"
|
||||
fill="none"
|
||||
>
|
||||
<g filter="url(#filter0_d_4930_46244)">
|
||||
<g filter="url(#filter1_i_4930_46244)">
|
||||
<path
|
||||
d="M11.4922 5.7205C11.4922 5.2902 11.7675 4.90814 12.1756 4.77193L16.1689 3.43934C16.8165 3.22323 17.4855 3.70522 17.4855 4.38792V12H11.4922V5.7205Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<rect x="4.5" y="32.5" width="20" height="75.5" fill="#303030" />
|
||||
<rect
|
||||
x="4.5"
|
||||
y="32.5"
|
||||
width="20"
|
||||
height="75.5"
|
||||
fill="url(#paint0_linear_4930_46244)"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M9.24747 11.5H19.7525V13.3774C19.7525 19.0605 20.9635 24.6784 23.3049 29.8568L24.5 32.5H4.5L5.69508 29.8568C8.03645 24.6784 9.24747 19.0605 9.24747 13.3774V11.5Z"
|
||||
fill="#3D3D3D"
|
||||
/>
|
||||
<path
|
||||
d="M9.24747 11.5H19.7525V13.3774C19.7525 19.0605 20.9635 24.6784 23.3049 29.8568L24.5 32.5H4.5L5.69508 29.8568C8.03645 24.6784 9.24747 19.0605 9.24747 13.3774V11.5Z"
|
||||
fill="url(#paint1_linear_4930_46244)"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M9.24747 11.5H19.7525V13.3774C19.7525 19.0605 20.9635 24.6784 23.3049 29.8568L24.5 32.5H4.5L5.69508 29.8568C8.03645 24.6784 9.24747 19.0605 9.24747 13.3774V11.5Z"
|
||||
fill="url(#paint2_linear_4930_46244)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<rect x="4.5" y="37" width="20" height="8" fill="currentColor" />
|
||||
<rect
|
||||
x="4.5"
|
||||
y="37"
|
||||
width="20"
|
||||
height="8"
|
||||
fill="url(#paint3_linear_4930_46244)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4930_46244"
|
||||
x="0.5"
|
||||
y="0"
|
||||
width="28"
|
||||
height="116"
|
||||
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 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4930_46244"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4930_46244"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_i_4930_46244"
|
||||
x="11.4922"
|
||||
y="3.38721"
|
||||
width="5.99316"
|
||||
height="8.61279"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<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="1.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<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="shape"
|
||||
result="effect1_innerShadow_4930_46244"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4930_46244"
|
||||
x1="4.5"
|
||||
y1="57.6667"
|
||||
x2="24.5"
|
||||
y2="57.6667"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="0.4" stop-opacity="0" />
|
||||
<stop offset="0.49" stop-opacity="0" />
|
||||
<stop offset="0.825" />
|
||||
<stop offset="0.9" stop-color="#797979" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4930_46244"
|
||||
x1="8.38889"
|
||||
y1="23.5"
|
||||
x2="20.6608"
|
||||
y2="21.5398"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.112839" stop-opacity="0" />
|
||||
<stop offset="0.634555" stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_4930_46244"
|
||||
x1="1.5"
|
||||
y1="24.5"
|
||||
x2="28"
|
||||
y2="18.5"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="0.44" stop-opacity="0.5" />
|
||||
<stop offset="0.59" />
|
||||
<stop offset="1" stop-color="white" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_4930_46244"
|
||||
x1="4.5"
|
||||
y1="41"
|
||||
x2="24.5"
|
||||
y2="41"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="0.835" />
|
||||
<stop offset="1" stop-opacity="0.77" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const EdgelessHighlighterLightIcon = html`
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="29"
|
||||
height="63"
|
||||
viewBox="0 0 29 63"
|
||||
fill="none"
|
||||
>
|
||||
<g filter="url(#filter0_d_4665_35356)">
|
||||
<g filter="url(#filter1_i_4665_35356)">
|
||||
<path
|
||||
d="M11.4922 5.7205C11.4922 5.2902 11.7675 4.90814 12.1756 4.77193L16.1689 3.43934C16.8165 3.22323 17.4855 3.70522 17.4855 4.38792V12H11.4922V5.7205Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<rect x="4.5" y="32.5" width="20" height="75.5" fill="#F3F3F3" />
|
||||
<rect
|
||||
x="4.5"
|
||||
y="32.5"
|
||||
width="20"
|
||||
height="75.5"
|
||||
fill="url(#paint0_linear_4665_35356)"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M9.24747 11.5H19.7525V13.3774C19.7525 19.0605 20.9635 24.6784 23.3049 29.8568L24.5 32.5H4.5L5.69508 29.8568C8.03645 24.6784 9.24747 19.0605 9.24747 13.3774V11.5Z"
|
||||
fill="#FAFAFA"
|
||||
/>
|
||||
<path
|
||||
d="M9.24747 11.5H19.7525V13.3774C19.7525 19.0605 20.9635 24.6784 23.3049 29.8568L24.5 32.5H4.5L5.69508 29.8568C8.03645 24.6784 9.24747 19.0605 9.24747 13.3774V11.5Z"
|
||||
fill="url(#paint1_linear_4665_35356)"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<rect x="4.5" y="37" width="20" height="8" fill="currentColor" />
|
||||
<rect
|
||||
x="4.5"
|
||||
y="37"
|
||||
width="20"
|
||||
height="8"
|
||||
fill="url(#paint2_linear_4665_35356)"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4665_35356"
|
||||
x="0.5"
|
||||
y="0"
|
||||
width="28"
|
||||
height="116"
|
||||
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 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4665_35356"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4665_35356"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_i_4665_35356"
|
||||
x="11.4922"
|
||||
y="3.38721"
|
||||
width="5.99414"
|
||||
height="8.61279"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<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="1.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<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="shape"
|
||||
result="effect1_innerShadow_4665_35356"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4665_35356"
|
||||
x1="4.5"
|
||||
y1="69.1558"
|
||||
x2="24.5"
|
||||
y2="69.1558"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="0.53" stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4665_35356"
|
||||
x1="8.38889"
|
||||
y1="23.5"
|
||||
x2="20.6608"
|
||||
y2="21.5398"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.112839" stop-opacity="0" />
|
||||
<stop offset="0.634555" stop-opacity="0" />
|
||||
<stop offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_4665_35356"
|
||||
x1="4.5"
|
||||
y1="41"
|
||||
x2="24.5"
|
||||
y2="41"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-opacity="0" />
|
||||
<stop offset="0.835" stop-opacity="0.99" />
|
||||
<stop offset="1" stop-opacity="0.77" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const penIconMap = {
|
||||
dark: {
|
||||
brush: EdgelessBrushDarkIcon,
|
||||
highlighter: EdgelessHighlighterDarkIcon,
|
||||
},
|
||||
light: {
|
||||
brush: EdgelessBrushLightIcon,
|
||||
highlighter: EdgelessHighlighterLightIcon,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
import { adjustColorAlpha } from '@blocksuite/affine-components/color-picker';
|
||||
import { DefaultTheme } from '@blocksuite/affine-model';
|
||||
import {
|
||||
FeatureFlagService,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { computed, type Signal } from '@preact/signals-core';
|
||||
import { css, html, LitElement, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { Pen, PenMap } from './types';
|
||||
|
||||
export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
SignalWatcher(LitElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.pens {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
|
||||
.pen-wrapper {
|
||||
display: flex;
|
||||
height: 64px;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transform: translateY(10px);
|
||||
transition-property: color, transform;
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pen-wrapper:hover,
|
||||
.pen-wrapper:active,
|
||||
.pen-wrapper[data-active] {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
menu-divider {
|
||||
height: 24px;
|
||||
margin: 0 9px 0 70px;
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _theme$ = computed(() => {
|
||||
return this.edgeless.std.get(ThemeProvider).theme$.value;
|
||||
});
|
||||
|
||||
private readonly _onPickPen = (tool: Pen) => {
|
||||
this.pen$.value = tool;
|
||||
this.setEdgelessTool(tool);
|
||||
};
|
||||
|
||||
private readonly _onPickColor = (e: ColorEvent) => {
|
||||
let color = e.detail.value;
|
||||
if (this.pen$.peek() === 'highlighter') {
|
||||
color = adjustColorAlpha(color, 0.5);
|
||||
}
|
||||
this.onChange({ color });
|
||||
};
|
||||
|
||||
override type: Pen[] = ['brush', 'highlighter'];
|
||||
|
||||
override render() {
|
||||
const {
|
||||
_theme$: { value: theme },
|
||||
color$: { value: currentColor },
|
||||
colors$: {
|
||||
value: { brush: brushColor, highlighter: highlighterColor },
|
||||
},
|
||||
pen$: { value: pen },
|
||||
penIconMap$: {
|
||||
value: { brush: brushIcon, highlighter: highlighterIcon },
|
||||
},
|
||||
} = this;
|
||||
|
||||
return html`
|
||||
<edgeless-slide-menu>
|
||||
<div class="menu-content">
|
||||
<div class="pens">
|
||||
<div
|
||||
class="pen-wrapper edgeless-brush-button"
|
||||
?data-active="${pen === 'brush'}"
|
||||
style=${styleMap({ color: brushColor })}
|
||||
@click=${() => this._onPickPen('brush')}
|
||||
>
|
||||
${brushIcon}
|
||||
</div>
|
||||
<div
|
||||
class="pen-wrapper edgeless-highlighter-button"
|
||||
?data-active="${pen === 'highlighter'}"
|
||||
style=${styleMap({ color: highlighterColor })}
|
||||
@click=${() => this._onPickPen('highlighter')}
|
||||
>
|
||||
${highlighterIcon}
|
||||
</div>
|
||||
</div>
|
||||
<menu-divider .vertical=${true}></menu-divider>
|
||||
<edgeless-color-panel
|
||||
class="one-way"
|
||||
@select=${this._onPickColor}
|
||||
.value=${currentColor}
|
||||
.theme=${theme}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
.shouldKeepColor=${true}
|
||||
.hasTransparent=${!this.edgeless.doc
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_color_picker')}
|
||||
></edgeless-color-panel>
|
||||
</div>
|
||||
</edgeless-slide-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onChange!: (props: Record<string, unknown>) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor colors$!: Signal<PenMap<string>>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor color$!: Signal<string>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor pen$!: Signal<Pen>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor penIconMap$!: Signal<PenMap<TemplateResult>>;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { keepColor } from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
EditPropsStore,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import { penIconMap } from './icons';
|
||||
import type { Pen } from './types';
|
||||
|
||||
export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
SignalWatcher(LitElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.edgeless-pen-button {
|
||||
height: 100%;
|
||||
}
|
||||
.pen-wrapper {
|
||||
width: 35px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
.pen-wrapper svg {
|
||||
transition-property: color, transform;
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
.edgeless-pen-button:hover .pen-wrapper svg,
|
||||
.pen-wrapper.active svg {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
get themeProvider() {
|
||||
return this.edgeless.std.get(ThemeProvider);
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return this.edgeless.std.get(EditPropsStore);
|
||||
}
|
||||
|
||||
private readonly colors$ = computed(() => {
|
||||
const theme = this.themeProvider.theme$.value;
|
||||
const brush = this.settings.lastProps$.value.brush.color;
|
||||
const highlighter = this.settings.lastProps$.value.highlighter.color;
|
||||
return {
|
||||
brush: keepColor(
|
||||
this.themeProvider.generateColorProperty(brush, undefined, theme)
|
||||
),
|
||||
highlighter: keepColor(
|
||||
this.themeProvider.generateColorProperty(highlighter, undefined, theme)
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
private readonly color$ = computed(() => {
|
||||
const pen = this.pen$.value;
|
||||
return this.colors$.value[pen];
|
||||
});
|
||||
|
||||
private readonly penIconMap$ = computed(() => {
|
||||
const theme = this.themeProvider.app$.value;
|
||||
return penIconMap[theme];
|
||||
});
|
||||
|
||||
private readonly penIcon$ = computed(() => {
|
||||
const pen = this.pen$.value;
|
||||
return this.penIconMap$.value[pen];
|
||||
});
|
||||
|
||||
private readonly pen$ = signal<Pen>('brush');
|
||||
|
||||
override enableActiveBackground = true;
|
||||
|
||||
override type: Pen[] = ['brush', 'highlighter'];
|
||||
|
||||
private _togglePenMenu() {
|
||||
if (this.tryDisposePopper()) return;
|
||||
!this.active && this.setEdgelessTool(this.pen$.peek());
|
||||
const menu = this.createPopper('edgeless-pen-menu', this);
|
||||
Object.assign(menu.element, {
|
||||
color$: this.color$,
|
||||
colors$: this.colors$,
|
||||
pen$: this.pen$,
|
||||
penIconMap$: this.penIconMap$,
|
||||
edgeless: this.edgeless,
|
||||
onChange: (props: Record<string, unknown>) => {
|
||||
const pen = this.pen$.peek();
|
||||
this.edgeless.std.get(EditPropsStore).recordLastProps(pen, props);
|
||||
this.setEdgelessTool(pen);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
const {
|
||||
active,
|
||||
penIcon$: { value: icon },
|
||||
color$: { value: color },
|
||||
} = this;
|
||||
|
||||
return html`
|
||||
<edgeless-toolbar-button
|
||||
class="edgeless-pen-button"
|
||||
.tooltip=${when(
|
||||
this.popper,
|
||||
() => nothing,
|
||||
() =>
|
||||
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._togglePenMenu()}
|
||||
>
|
||||
<div style=${styleMap({ color })} class="pen-wrapper">${icon}</div>
|
||||
</edgeless-toolbar-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
|
||||
|
||||
export type Pen = Extract<
|
||||
GfxToolsFullOptionValue['type'],
|
||||
'brush' | 'highlighter'
|
||||
>;
|
||||
|
||||
export type PenMap<T> = Record<Pen, T>;
|
||||
123
blocksuite/affine/gfx/brush/src/toolbar/configs/highlighter.ts
Normal file
123
blocksuite/affine/gfx/brush/src/toolbar/configs/highlighter.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
adjustColorAlpha,
|
||||
keepColor,
|
||||
packColor,
|
||||
type PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
DEFAULT_HIGHLIGHTER_LINE_WIDTH,
|
||||
DefaultTheme,
|
||||
HIGHLIGHTER_LINE_WIDTHS,
|
||||
HighlighterElementModel,
|
||||
resolveColor,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
type ToolbarModuleConfig,
|
||||
ToolbarModuleExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
getMostCommonResolvedValue,
|
||||
getMostCommonValue,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/block-std';
|
||||
import { html } from 'lit';
|
||||
|
||||
export const highlighterToolbarConfig = {
|
||||
actions: [
|
||||
{
|
||||
id: 'a.line-width',
|
||||
content(ctx) {
|
||||
const models = ctx.getSurfaceModelsByType(HighlighterElementModel);
|
||||
if (!models.length) return null;
|
||||
|
||||
const lineWidth =
|
||||
getMostCommonValue(models, 'lineWidth') ??
|
||||
DEFAULT_HIGHLIGHTER_LINE_WIDTH;
|
||||
const onPick = (e: CustomEvent<number>) => {
|
||||
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
|
||||
.config=${{
|
||||
width: 140,
|
||||
itemSize: 16,
|
||||
itemIconSize: 8,
|
||||
dragHandleSize: 14,
|
||||
count: HIGHLIGHTER_LINE_WIDTHS.length,
|
||||
}}
|
||||
.lineWidths=${HIGHLIGHTER_LINE_WIDTHS}
|
||||
.selectedSize=${lineWidth}
|
||||
@select=${onPick}
|
||||
>
|
||||
</edgeless-line-width-panel>
|
||||
`;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'b.color-picker',
|
||||
content(ctx) {
|
||||
const models = ctx.getSurfaceModelsByType(HighlighterElementModel);
|
||||
if (!models.length) return null;
|
||||
|
||||
const theme = ctx.theme.edgeless$.value;
|
||||
|
||||
const field = 'color';
|
||||
const firstModel = models[0];
|
||||
const originalColor = firstModel[field];
|
||||
const color = keepColor(
|
||||
getMostCommonResolvedValue(models, field, color =>
|
||||
resolveColor(color, theme)
|
||||
) ?? resolveColor(DefaultTheme.black, theme)
|
||||
);
|
||||
const onPick = (e: PickColorEvent) => {
|
||||
if (e.type === 'pick') {
|
||||
const color = adjustColorAlpha(e.detail.value, 0.5);
|
||||
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
|
||||
.colorPanelClass="${'one-way small'}"
|
||||
.label="${'Color'}"
|
||||
.pick=${onPick}
|
||||
.color=${color}
|
||||
.theme=${theme}
|
||||
.originalColor=${originalColor}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
.shouldKeepColor=${true}
|
||||
.enableCustomColor=${false}
|
||||
>
|
||||
</edgeless-color-picker-button>
|
||||
`;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
when: ctx => ctx.getSurfaceModelsByType(HighlighterElementModel).length > 0,
|
||||
} as const satisfies ToolbarModuleConfig;
|
||||
|
||||
export const highlighterToolbarExtension = ToolbarModuleExtension({
|
||||
id: BlockFlavourIdentifier('affine:surface:highlighter'),
|
||||
config: highlighterToolbarConfig,
|
||||
});
|
||||
2
blocksuite/affine/gfx/brush/src/toolbar/configs/index.ts
Normal file
2
blocksuite/affine/gfx/brush/src/toolbar/configs/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './brush';
|
||||
export * from './highlighter';
|
||||
@@ -4,10 +4,8 @@ import { html } from 'lit';
|
||||
export 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>
|
||||
content: html`<div class="pen-and-eraser">
|
||||
<edgeless-pen-tool-button .edgeless=${block}></edgeless-pen-tool-button>
|
||||
|
||||
<edgeless-eraser-tool-button
|
||||
.edgeless=${block}
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum LineWidth {
|
||||
Twelve = 12,
|
||||
}
|
||||
|
||||
export const LINE_WIDTHS = [
|
||||
export const BRUSH_LINE_WIDTHS = [
|
||||
LineWidth.Two,
|
||||
LineWidth.Four,
|
||||
LineWidth.Six,
|
||||
@@ -22,6 +22,10 @@ export const LINE_WIDTHS = [
|
||||
LineWidth.Twelve,
|
||||
];
|
||||
|
||||
export const HIGHLIGHTER_LINE_WIDTHS = [10, 14, 18, 22, 26, 30];
|
||||
|
||||
export const DEFAULT_HIGHLIGHTER_LINE_WIDTH = 22;
|
||||
|
||||
/**
|
||||
* Use `DefaultTheme.StrokeColorShortMap` instead.
|
||||
*
|
||||
|
||||
223
blocksuite/affine/model/src/elements/highlighter/highlighter.ts
Normal file
223
blocksuite/affine/model/src/elements/highlighter/highlighter.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import type {
|
||||
BaseElementProps,
|
||||
PointTestOptions,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
convert,
|
||||
derive,
|
||||
field,
|
||||
GfxPrimitiveElementModel,
|
||||
watch,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
Bound,
|
||||
getBoundFromPoints,
|
||||
getPointsFromBoundWithRotation,
|
||||
getQuadBoundWithRotation,
|
||||
getSolidStrokePoints,
|
||||
getSvgPathFromStroke,
|
||||
inflateBound,
|
||||
isPointOnlines,
|
||||
type IVec,
|
||||
type IVec3,
|
||||
lineIntersects,
|
||||
PointLocation,
|
||||
polyLineNearestPoint,
|
||||
type SerializedXYWH,
|
||||
transformPointsToNewBound,
|
||||
Vec,
|
||||
} from '@blocksuite/global/gfx';
|
||||
|
||||
import { DEFAULT_HIGHLIGHTER_LINE_WIDTH } from '../../consts';
|
||||
import { type Color, DefaultTheme } from '../../themes/index';
|
||||
|
||||
export type HighlighterProps = BaseElementProps & {
|
||||
/**
|
||||
* [[x0,y0,pressure0?],[x1,y1,pressure1?]...]
|
||||
* pressure is optional and exsits when pressure sensitivity is supported, otherwise not.
|
||||
*/
|
||||
points: number[][];
|
||||
color: Color;
|
||||
lineWidth: number;
|
||||
};
|
||||
|
||||
export class HighlighterElementModel extends GfxPrimitiveElementModel<HighlighterProps> {
|
||||
/**
|
||||
* The SVG path commands for the brush.
|
||||
*/
|
||||
get commands() {
|
||||
if (!this._local.has('commands')) {
|
||||
const stroke = getSolidStrokePoints(this.points ?? [], this.lineWidth);
|
||||
const commands = getSvgPathFromStroke(stroke);
|
||||
|
||||
this._local.set('commands', commands);
|
||||
}
|
||||
|
||||
return this._local.get('commands') as string;
|
||||
}
|
||||
|
||||
override get connectable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
override get type() {
|
||||
return 'highlighter';
|
||||
}
|
||||
|
||||
override containsBound(bounds: Bound) {
|
||||
const points = getPointsFromBoundWithRotation(this);
|
||||
return points.some(point => bounds.containsPoint(point));
|
||||
}
|
||||
|
||||
override getLineIntersections(start: IVec, end: IVec) {
|
||||
const tl = [this.x, this.y];
|
||||
const points = getPointsFromBoundWithRotation(this, _ =>
|
||||
this.points.map(point => Vec.add(point, tl))
|
||||
);
|
||||
|
||||
const box = Bound.fromDOMRect(getQuadBoundWithRotation(this));
|
||||
|
||||
if (box.w < 8 && box.h < 8) {
|
||||
return Vec.distanceToLineSegment(start, end, box.center) < 5 ? [] : null;
|
||||
}
|
||||
|
||||
if (box.intersectLine(start, end, true)) {
|
||||
const len = points.length;
|
||||
for (let i = 1; i < len; i++) {
|
||||
const result = lineIntersects(start, end, points[i - 1], points[i]);
|
||||
if (result) {
|
||||
return [
|
||||
new PointLocation(
|
||||
result,
|
||||
Vec.normalize(Vec.sub(points[i], points[i - 1]))
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
override getNearestPoint(point: IVec): IVec {
|
||||
const { x, y } = this;
|
||||
|
||||
return polyLineNearestPoint(
|
||||
this.points.map(p => Vec.add(p, [x, y])),
|
||||
point
|
||||
) as IVec;
|
||||
}
|
||||
|
||||
override getRelativePointLocation(position: IVec): PointLocation {
|
||||
const point = Bound.deserialize(this.xywh).getRelativePoint(position);
|
||||
return new PointLocation(point);
|
||||
}
|
||||
|
||||
override includesPoint(
|
||||
px: number,
|
||||
py: number,
|
||||
options?: PointTestOptions
|
||||
): boolean {
|
||||
const hit = isPointOnlines(
|
||||
Bound.deserialize(this.xywh),
|
||||
this.points as [number, number][],
|
||||
this.rotate,
|
||||
[px, py],
|
||||
(options?.hitThreshold ?? 10) / Math.min(options?.zoom ?? 1, 1)
|
||||
);
|
||||
return hit;
|
||||
}
|
||||
|
||||
@field()
|
||||
accessor color: Color = DefaultTheme.hightlighterColor;
|
||||
|
||||
@watch((_, instance) => {
|
||||
instance['_local'].delete('commands');
|
||||
})
|
||||
@derive((lineWidth: number, instance: Instance) => {
|
||||
const oldBound = instance.elementBound;
|
||||
|
||||
if (
|
||||
lineWidth === instance.lineWidth ||
|
||||
oldBound.w === 0 ||
|
||||
oldBound.h === 0
|
||||
)
|
||||
return {};
|
||||
|
||||
const points = instance.points;
|
||||
const transformed = transformPointsToNewBound(
|
||||
points.map(([x, y]) => ({ x, y })),
|
||||
oldBound,
|
||||
instance.lineWidth / 2,
|
||||
inflateBound(oldBound, lineWidth - instance.lineWidth),
|
||||
lineWidth / 2
|
||||
);
|
||||
|
||||
return {
|
||||
points: transformed.points.map((p, i) => [
|
||||
p.x,
|
||||
p.y,
|
||||
...(points[i][2] !== undefined ? [points[i][2]] : []),
|
||||
]),
|
||||
xywh: transformed.bound.serialize(),
|
||||
};
|
||||
})
|
||||
@field()
|
||||
accessor lineWidth: number = DEFAULT_HIGHLIGHTER_LINE_WIDTH;
|
||||
|
||||
@watch((_, instance) => {
|
||||
instance['_local'].delete('commands');
|
||||
})
|
||||
@derive((points: IVec[], instance: Instance) => {
|
||||
const lineWidth = instance.lineWidth;
|
||||
const bound = getBoundFromPoints(points);
|
||||
const boundWidthLineWidth = inflateBound(bound, lineWidth);
|
||||
|
||||
return {
|
||||
xywh: boundWidthLineWidth.serialize(),
|
||||
};
|
||||
})
|
||||
@convert((points: (IVec | IVec3)[], instance) => {
|
||||
const lineWidth = instance.lineWidth;
|
||||
const bound = getBoundFromPoints(points as IVec[]);
|
||||
const boundWidthLineWidth = inflateBound(bound, lineWidth);
|
||||
const relativePoints = points.map(([x, y, pressure]) => [
|
||||
x - boundWidthLineWidth.x,
|
||||
y - boundWidthLineWidth.y,
|
||||
...(pressure !== undefined ? [pressure] : []),
|
||||
]);
|
||||
|
||||
return relativePoints;
|
||||
})
|
||||
@field()
|
||||
accessor points: (IVec | IVec3)[] = [];
|
||||
|
||||
@field(0)
|
||||
accessor rotate: number = 0;
|
||||
|
||||
@derive((xywh: SerializedXYWH, instance: Instance) => {
|
||||
const bound = Bound.deserialize(xywh);
|
||||
|
||||
if (bound.w === instance.w && bound.h === instance.h) return {};
|
||||
|
||||
const { lineWidth } = instance;
|
||||
const transformed = transformPointsToNewBound(
|
||||
instance.points.map(([x, y]) => ({ x, y })),
|
||||
instance,
|
||||
instance.lineWidth / 2,
|
||||
bound,
|
||||
lineWidth / 2
|
||||
);
|
||||
|
||||
return {
|
||||
points: transformed.points.map((p, i) => [
|
||||
p.x,
|
||||
p.y,
|
||||
...(instance.points[i][2] !== undefined ? [instance.points[i][2]] : []),
|
||||
]),
|
||||
};
|
||||
})
|
||||
@field()
|
||||
accessor xywh: SerializedXYWH = '[0,0,0,0]';
|
||||
}
|
||||
|
||||
type Instance = GfxPrimitiveElementModel<HighlighterProps> & HighlighterProps;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './highlighter';
|
||||
@@ -2,6 +2,7 @@ import type { EdgelessTextBlockModel } from '../blocks/edgeless-text/edgeless-te
|
||||
import type { BrushElementModel } from './brush/index.js';
|
||||
import type { ConnectorElementModel } from './connector/index.js';
|
||||
import type { GroupElementModel } from './group/index.js';
|
||||
import type { HighlighterElementModel } from './highlighter/index.js';
|
||||
import type { MindmapElementModel } from './mindmap/index.js';
|
||||
import type { ShapeElementModel } from './shape/index.js';
|
||||
import type { TextElementModel } from './text/index.js';
|
||||
@@ -9,12 +10,14 @@ import type { TextElementModel } from './text/index.js';
|
||||
export * from './brush/index.js';
|
||||
export * from './connector/index.js';
|
||||
export * from './group/index.js';
|
||||
export * from './highlighter/index.js';
|
||||
export * from './mindmap/index.js';
|
||||
export * from './shape/index.js';
|
||||
export * from './text/index.js';
|
||||
|
||||
export type SurfaceElementModelMap = {
|
||||
brush: BrushElementModel;
|
||||
highlighter: HighlighterElementModel;
|
||||
connector: ConnectorElementModel;
|
||||
group: GroupElementModel;
|
||||
mindmap: MindmapElementModel;
|
||||
|
||||
@@ -124,6 +124,8 @@ export const DefaultTheme: Theme = {
|
||||
shapeFillColor: Medium.Yellow,
|
||||
connectorColor: Medium.Grey,
|
||||
noteBackgrounColor: NoteBackgroundColorMap.White,
|
||||
// 50% transparent `Default.black`
|
||||
hightlighterColor: { dark: '#ffffff80', light: '#00000080' },
|
||||
Palettes,
|
||||
ShapeTextColorPalettes,
|
||||
NoteBackgroundColorMap,
|
||||
|
||||
@@ -21,6 +21,7 @@ export const ThemeSchema = z.object({
|
||||
shapeFillColor: ColorSchema,
|
||||
connectorColor: ColorSchema,
|
||||
noteBackgrounColor: ColorSchema,
|
||||
hightlighterColor: ColorSchema,
|
||||
|
||||
// Universal color palettes
|
||||
Palettes: z.array(PaletteSchema),
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ConnectorMode,
|
||||
DEFAULT_CONNECTOR_MODE,
|
||||
DEFAULT_FRONT_ENDPOINT_STYLE,
|
||||
DEFAULT_HIGHLIGHTER_LINE_WIDTH,
|
||||
DEFAULT_REAR_ENDPOINT_STYLE,
|
||||
DEFAULT_ROUGHNESS,
|
||||
DefaultTheme,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
FontWeight,
|
||||
FontWeightSchema,
|
||||
FrameZodSchema,
|
||||
HIGHLIGHTER_LINE_WIDTHS,
|
||||
LayoutType,
|
||||
LineWidth,
|
||||
MindmapStyle,
|
||||
@@ -89,6 +91,19 @@ export const BrushSchema = z
|
||||
lineWidth: LineWidth.Four,
|
||||
});
|
||||
|
||||
export const HighlighterSchema = z
|
||||
.object({
|
||||
color: ColorSchema,
|
||||
lineWidth: z
|
||||
.number()
|
||||
.int()
|
||||
.refine(value => HIGHLIGHTER_LINE_WIDTHS.includes(value)),
|
||||
})
|
||||
.default({
|
||||
color: DefaultTheme.hightlighterColor,
|
||||
lineWidth: DEFAULT_HIGHLIGHTER_LINE_WIDTH,
|
||||
});
|
||||
|
||||
const DEFAULT_SHAPE = {
|
||||
color: DefaultTheme.shapeTextColor,
|
||||
fillColor: DefaultTheme.shapeFillColor,
|
||||
@@ -162,6 +177,7 @@ export const MindmapSchema = z
|
||||
export const NodePropsSchema = z.object({
|
||||
connector: ConnectorSchema,
|
||||
brush: BrushSchema,
|
||||
highlighter: HighlighterSchema,
|
||||
text: TextSchema,
|
||||
mindmap: MindmapSchema,
|
||||
'affine:edgeless-text': EdgelessTextZodSchema,
|
||||
|
||||
@@ -145,7 +145,7 @@ export class EdgelessToolbarWidget extends WidgetComponent<RootBlockModel> {
|
||||
height: 100%;
|
||||
background-color: var(--affine-border-color);
|
||||
}
|
||||
.brush-and-eraser {
|
||||
.pen-and-eraser {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
gap: 4px;
|
||||
|
||||
Reference in New Issue
Block a user