feat(editor): store real color values in edgeless (#9254)

### What's Changed!

* adds theme type: `ThemeSchema`
* adds default theme: `DefaultTheme`
* stores real color values
This commit is contained in:
fundon
2024-12-30 03:36:35 +00:00
parent a5641ae608
commit b9f2650369
287 changed files with 2219 additions and 1520 deletions
@@ -14,11 +14,8 @@ import type {
ShapeElementModel,
} from '@blocksuite/affine-model';
import {
DEFAULT_NOTE_BACKGROUND_COLOR,
DEFAULT_NOTE_WIDTH,
DEFAULT_SHAPE_FILL_COLOR,
DEFAULT_SHAPE_STROKE_COLOR,
DEFAULT_TEXT_COLOR,
DefaultTheme,
FontFamily,
FontStyle,
FontWeight,
@@ -282,7 +279,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
textAlign: 'left',
fontSize: 24,
fontFamily: FontFamily.Inter,
color: DEFAULT_TEXT_COLOR,
color: DefaultTheme.textColor,
fontWeight: FontWeight.Regular,
fontStyle: FontStyle.Normal,
});
@@ -474,7 +471,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
.getColorValue(
this.edgeless.std.get(EditPropsStore).lastProps$.value['affine:note']
.background,
DEFAULT_NOTE_BACKGROUND_COLOR,
DefaultTheme.noteBackgrounColor,
true
);
this._overlay = new AutoCompleteNoteOverlay(this.gfx, xywh, background);
@@ -515,10 +512,10 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
const stroke = this.edgeless.std
.get(ThemeProvider)
.getColorValue(strokeColor, DEFAULT_SHAPE_STROKE_COLOR, true);
.getColorValue(strokeColor, DefaultTheme.shapeStrokeColor, true);
const fill = this.edgeless.std
.get(ThemeProvider)
.getColorValue(fillColor, DEFAULT_SHAPE_FILL_COLOR, true);
.getColorValue(fillColor, DefaultTheme.shapeFillColor, true);
const options = {
seed: 666,
@@ -21,7 +21,7 @@ import type {
} from '@blocksuite/affine-model';
import {
DEFAULT_NOTE_HEIGHT,
DEFAULT_SHAPE_STROKE_COLOR,
DefaultTheme,
LayoutType,
MindmapElementModel,
ShapeElementModel,
@@ -643,7 +643,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
this._autoCompleteOverlay.stroke = surface.renderer.getColorValue(
current.strokeColor,
DEFAULT_SHAPE_STROKE_COLOR,
DefaultTheme.shapeStrokeColor,
true
);
this._autoCompleteOverlay.linePoints = path;
@@ -1,4 +1,6 @@
import type { EditorMenuButton } from '@blocksuite/affine-components/toolbar';
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
import { resolveColor } from '@blocksuite/affine-model';
import { WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement } from 'lit';
import { property, query, state } from 'lit/decorators.js';
@@ -7,19 +9,14 @@ import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js';
import type { ColorEvent } from '../panel/color-panel.js';
import type {
ModeType,
PickColorDetail,
PickColorEvent,
PickColorType,
} from './types.js';
import type { ModeType, PickColorEvent, PickColorType } from './types.js';
import { keepColor, preprocessColor, rgbaToHex8 } from './utils.js';
type Type = 'normal' | 'custom';
export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
readonly #select = (e: ColorEvent) => {
this.#pick({ palette: e.detail });
this.#pick(e.detail);
};
switchToCustomTab = (e: MouseEvent) => {
@@ -69,14 +66,16 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
}
get isCustomColor() {
return !this.palettes.includes(this.color);
return !this.palettes
.map(({ value }) => resolveColor(value, this.theme))
.includes(this.color);
}
get tabContentPadding() {
return `${this.tabType === 'custom' ? 0 : 8}px`;
}
#pick(detail: PickColorDetail) {
#pick(detail: Palette) {
this.pick?.({ type: 'start' });
this.pick?.({ type: 'pick', detail });
this.pick?.({ type: 'end' });
@@ -126,8 +125,8 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
role="listbox"
class=${ifDefined(this.colorPanelClass)}
.value=${this.color}
.theme=${this.theme}
.palettes=${this.palettes}
.options=${this.palettes}
.hollowCircle=${this.hollowCircle}
.openColorPicker=${this.switchToCustomTab}
.hasTransparent=${false}
@@ -189,7 +188,7 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
accessor menuButton!: EditorMenuButton;
@property({ attribute: false })
accessor palettes: string[] = [];
accessor palettes: Palette[] = [];
@property({ attribute: false })
accessor pick!: (event: PickColorEvent) => void;
@@ -197,6 +196,9 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
@state()
accessor tabType: Type = 'normal';
@property({ attribute: false })
accessor theme!: ColorScheme;
@property()
accessor tooltip: string | undefined = undefined;
}
@@ -1,3 +1,4 @@
import type { Color } from '@blocksuite/affine-model';
import { on, once, stopPropagation } from '@blocksuite/affine-shared/utils';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
import { batch, computed, signal } from '@preact/signals-core';
@@ -111,15 +112,15 @@ export class EdgelessColorPicker extends SignalWatcher(
#pick() {
const hsva = this.hsva$.peek();
const type = this.modeType$.peek();
const detail = { [type]: hsvaToHex8(hsva) };
const value = { [type]: hsvaToHex8(hsva) };
const key = 'Custom';
if (type !== 'normal') {
const another = type === 'light' ? 'dark' : 'light';
const { hsva } = this[`${another}$`].peek();
detail[another] = hsvaToHex8(hsva);
value[another] = hsvaToHex8(hsva);
}
this.pick?.({ type: 'pick', detail });
this.pick?.({ type: 'pick', detail: { key, value: value as Color } });
}
#pickEnd() {
@@ -398,12 +399,12 @@ export class EdgelessColorPicker extends SignalWatcher(
// Updates modes
if (modes?.length) {
batches.push(() => {
// eslint-disable-next-line sonarjs/no-ignored-return
this.modes$.value.reduce((fallback, curr, n) => {
let value = defaultHsva();
this.modes$.value.forEach((curr, n) => {
const m = modes[n];
curr.hsva = m ? rgbaToHsva(m.rgba) : fallback;
return { ...curr.hsva };
}, defaultHsva());
curr.hsva = m ? rgbaToHsva(m.rgba) : value;
value = curr.hsva;
});
});
}
@@ -11,8 +11,7 @@ export const COLORS: [Rgb, number][] = [
[{ r: 0, g: 1, b: 1 }, 3 / 6],
[{ r: 0, g: 0, b: 1 }, 4 / 6],
[{ r: 1, g: 0, b: 1 }, 5 / 6],
// eslint-disable-next-line sonarjs/no-identical-expressions
[{ r: 1, g: 0, b: 0 }, 6 / 6],
[{ r: 1, g: 0, b: 0 }, 1],
];
export const FIRST_COLOR = COLORS[0][0];
@@ -11,6 +11,7 @@ export class EdgelessColorCustomButton extends LitElement {
height: 24px;
cursor: pointer;
}
:host([active]):after {
position: absolute;
display: block;
@@ -1,6 +1,6 @@
// https://www.w3.org/TR/css-color-4/
import type { ColorScheme } from '@blocksuite/affine-model';
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
// Red, green, blue. All in the range [0, 1].
export type Rgb = {
@@ -48,8 +48,6 @@ export type ModeRgba = { type: ModeType; rgba: Rgba };
export type PickColorType = 'palette' | ModeType;
export type PickColorDetail = Partial<Record<PickColorType, string>>;
export type PickColorEvent =
| { type: 'start' | 'end' }
| { type: 'pick'; detail: PickColorDetail };
| { type: 'pick'; detail: Palette };
@@ -7,7 +7,6 @@ import type {
Hsv,
Hsva,
ModeType,
PickColorDetail,
PickColorType,
Point,
Rgb,
@@ -269,12 +268,13 @@ export const preprocessColor = (style: CSSStyleDeclaration) => {
*
* ```json
* { 'fillColor': '--affine-palette-shape-yellow' }
* { 'fillColor': '#ffffff' }
* { 'fillColor': { normal: '#ffffffff' }}
* { 'fillColor': { light: '#fff000ff', 'dark': '#0000fff00' }}
* ```
*/
export const packColor = (key: string, detail: PickColorDetail) => {
return { [key]: detail.palette ?? detail };
export const packColor = (key: string, color: Color) => {
return { [key]: typeof color === 'object' ? { ...color } : color };
};
/**
@@ -298,10 +298,14 @@ export const packColorsWithColorScheme = (
let type: PickColorType = 'palette';
if (typeof oldColor === 'object') {
type = colorScheme in oldColor ? colorScheme : 'normal';
colors[0].value = oldColor.normal ?? value;
colors[1].value = oldColor.light ?? value;
colors[2].value = oldColor.dark ?? value;
if ('normal' in oldColor) {
type = 'normal';
colors[0].value = oldColor.normal ?? value;
} else {
type = colorScheme;
colors[1].value = oldColor.light ?? value;
colors[2].value = oldColor.dark ?? value;
}
}
return { type, colors };
@@ -1,12 +1,15 @@
import { Black, ColorScheme, PALETTES, White } from '@blocksuite/affine-model';
import type { Color, ColorScheme, Palette } from '@blocksuite/affine-model';
import { isTransparent, resolveColor } from '@blocksuite/affine-model';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { css, html, LitElement, nothing, svg, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { repeat } from 'lit/directives/repeat.js';
import isEqual from 'lodash.isequal';
export class ColorEvent extends Event {
detail: string;
detail: Palette;
constructor(
type: string,
@@ -14,21 +17,13 @@ export class ColorEvent extends Event {
detail,
composed,
bubbles,
}: { detail: string; composed: boolean; bubbles: boolean }
}: { detail: Palette; composed: boolean; bubbles: boolean }
) {
super(type, { bubbles, composed });
this.detail = detail;
}
}
export const GET_DEFAULT_LINE_COLOR = (theme: ColorScheme) => {
return theme === ColorScheme.Dark ? White : Black;
};
export function isTransparent(color: string) {
return color.toLowerCase().endsWith('transparent');
}
function TransparentIcon(hollowCircle = false) {
const CircleIcon: TemplateResult | typeof nothing = hollowCircle
? svg`<circle cx="10" cy="10" r="8" fill="white" />`
@@ -125,7 +120,7 @@ export class EdgelessColorButton extends LitElement {
border-radius: 50%;
overflow: hidden;
}
:host .color-unit:after {
:host .color-unit::after {
position: absolute;
display: block;
content: '';
@@ -134,7 +129,6 @@ export class EdgelessColorButton extends LitElement {
border-radius: 50%;
box-sizing: border-box;
overflow: hidden;
pointer-events: none;
border-width: 0.5px;
border-style: solid;
border-color: ${unsafeCSSVarV2('layer/insideBorder/blackBorder')};
@@ -151,34 +145,32 @@ export class EdgelessColorButton extends LitElement {
width: 20px;
height: 20px;
}
:host([active]):after {
:host::after {
position: absolute;
display: block;
content: '';
width: 27px;
height: 27px;
border: 1.5px solid var(--affine-primary-color);
border-radius: 50%;
box-sizing: border-box;
overflow: hidden;
pointer-events: none;
}
:host([active])::after {
border: 1.5px solid var(--affine-primary-color);
}
`;
get preprocessColor() {
const color = this.color;
return color.startsWith('--') ? `var(${color})` : color;
const value = resolveColor(this.color, this.theme);
return value.startsWith('--') ? `var(${value})` : value;
}
override render() {
const { color, preprocessColor, hollowCircle, letter } = this;
const { label, preprocessColor, hollowCircle } = this;
const additionIcon = AdditionIcon(preprocessColor, !!hollowCircle);
return html`<div
class="color-unit"
aria-label=${color}
data-letter=${letter ? 'A' : nothing}
>
return html`<div class="color-unit" aria-label=${ifDefined(label)}>
${additionIcon}
</div>`;
}
@@ -187,13 +179,16 @@ export class EdgelessColorButton extends LitElement {
accessor active: boolean = false;
@property({ attribute: false })
accessor color!: string;
accessor color!: Color;
@property({ attribute: false })
accessor hollowCircle: boolean = false;
@property({ attribute: false })
accessor letter: boolean | undefined = undefined;
accessor label: string | undefined = undefined;
@property({ attribute: false })
accessor theme!: ColorScheme;
}
export class EdgelessColorPanel extends LitElement {
@@ -221,35 +216,42 @@ export class EdgelessColorPanel extends LitElement {
}
`;
onSelect(value: string) {
onSelect(palette: Palette) {
this.dispatchEvent(
new ColorEvent('select', {
detail: value,
detail: palette,
composed: true,
bubbles: true,
})
);
this.value = value;
}
get resolvedValue() {
return this.value && resolveColor(this.value, this.theme);
}
override render() {
const resolvedValue = this.resolvedValue;
return html`
${repeat(
this.palettes,
color => color,
color =>
html`<edgeless-color-button
class=${classMap({
large: true,
black: color.startsWith('--') && color.endsWith('black'),
})}
.color=${color}
.letter=${this.showLetterMark}
palette => palette.key,
palette => {
const resolvedColor = resolveColor(palette.value, this.theme);
return html`<edgeless-color-button
class=${classMap({ large: true })}
.label=${palette.key}
.color=${palette.value}
.theme=${this.theme}
.hollowCircle=${this.hollowCircle}
?active=${color === this.value}
@click=${() => this.onSelect(color)}
?active=${isEqual(resolvedColor, resolvedValue)}
@click=${() => {
this.onSelect(palette);
this.value = resolvedColor;
}}
>
</edgeless-color-button>`
</edgeless-color-button>`;
}
)}
<slot name="custom"></slot>
`;
@@ -265,13 +267,13 @@ export class EdgelessColorPanel extends LitElement {
accessor openColorPicker!: (e: MouseEvent) => void;
@property({ type: Array })
accessor palettes: readonly string[] = PALETTES;
accessor palettes: readonly Palette[] = [];
@property({ attribute: false })
accessor showLetterMark = false;
accessor theme!: ColorScheme;
@property({ attribute: false })
accessor value: string | null = null;
accessor value: Color | null = null;
}
export class EdgelessTextColorIcon extends LitElement {
@@ -3,7 +3,7 @@ import {
DashLineIcon,
StraightLineIcon,
} from '@blocksuite/affine-components/icons';
import { type LineWidth, StrokeStyle } from '@blocksuite/affine-model';
import { LineWidth, StrokeStyle } from '@blocksuite/affine-model';
import { html } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { repeat } from 'lit/directives/repeat.js';
@@ -47,14 +47,14 @@ const LINE_STYLE_LIST = [
export function LineStylesPanel({
onClick,
selectedLineSize,
selectedLineStyle,
selectedLineSize = LineWidth.Two,
lineStyles = [StrokeStyle.Solid, StrokeStyle.Dash, StrokeStyle.None],
}: LineStylesPanelProps = {}) {
const lineSizePanel = html`
<edgeless-line-width-panel
.selectedSize=${selectedLineSize as LineWidth}
.disable=${selectedLineStyle === StrokeStyle.None}
?disabled=${selectedLineStyle === StrokeStyle.None}
.selectedSize=${selectedLineSize}
@select=${(e: LineWidthEvent) => {
onClick?.({
type: 'size',
@@ -69,15 +69,15 @@ export function LineStylesPanel({
item => item.value,
({ name, icon, value }) => {
const active = selectedLineStyle === value;
const classes: Record<string, boolean> = {
const classInfo = {
'line-style-button': true,
[`mode-${value}`]: true,
};
if (active) classes['active'] = true;
if (active) classInfo['active'] = true;
return html`
<edgeless-tool-icon-button
class=${classMap(classes)}
class=${classMap(classInfo)}
.active=${active}
.activeMode=${'background'}
.tooltip=${name}
@@ -1,4 +1,8 @@
import { PALETTES, type StrokeStyle } from '@blocksuite/affine-model';
import {
type ColorScheme,
DefaultTheme,
type StrokeStyle,
} from '@blocksuite/affine-model';
import { WithDisposable } from '@blocksuite/global/utils';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
@@ -38,8 +42,9 @@ export class StrokeStylePanel extends WithDisposable(LitElement) {
<edgeless-color-panel
role="listbox"
aria-label="Border colors"
.palettes=${PALETTES}
.value=${this.strokeColor}
.theme=${this.theme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${this.hollowCircle}
@select=${(e: ColorEvent) => this.setStrokeColor(e)}
>
@@ -64,6 +69,9 @@ export class StrokeStylePanel extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor strokeWidth!: number;
@property({ attribute: false })
accessor theme!: ColorScheme;
}
declare global {
@@ -1,4 +1,4 @@
import { STROKE_COLORS } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
import {
EditPropsStore,
ThemeProvider,
@@ -9,10 +9,7 @@ import { computed } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import {
type ColorEvent,
GET_DEFAULT_LINE_COLOR,
} from '../../panel/color-panel.js';
import type { ColorEvent } from '../../panel/color-panel.js';
import type { LineWidthEvent } from '../../panel/line-width-panel.js';
import { EdgelessToolbarToolMixin } from '../mixins/tool.mixin.js';
@@ -46,14 +43,13 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
};
});
private readonly _theme$ = computed(() => {
return this.edgeless.std.get(ThemeProvider).theme$.value;
});
type: GfxToolsFullOptionValue['type'] = 'brush';
override render() {
const theme = this.edgeless.std.get(ThemeProvider).theme;
const color = this.edgeless.std
.get(ThemeProvider)
.getColorValue(this._props$.value.color, GET_DEFAULT_LINE_COLOR(theme));
return html`
<edgeless-slide-menu>
<div class="menu-content">
@@ -66,12 +62,14 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
<menu-divider .vertical=${true}></menu-divider>
<edgeless-color-panel
class="one-way"
.value=${color}
.palettes=${STROKE_COLORS}
.value=${this._props$.value.color}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc.awarenessStore.getFlag(
'enable_color_picker'
)}
@select=${(e: ColorEvent) => this.onChange({ color: e.detail })}
@select=${(e: ColorEvent) =>
this.onChange({ color: e.detail.value })}
></edgeless-color-panel>
</div>
</edgeless-slide-menu>
@@ -3,11 +3,7 @@ import {
ConnectorLWithArrowIcon,
ConnectorXWithArrowIcon,
} from '@blocksuite/affine-components/icons';
import {
ConnectorMode,
DEFAULT_CONNECTOR_COLOR,
STROKE_COLORS,
} from '@blocksuite/affine-model';
import { ConnectorMode, DefaultTheme } from '@blocksuite/affine-model';
import {
EditPropsStore,
ThemeProvider,
@@ -104,6 +100,10 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
return { mode, stroke, strokeWidth };
});
private readonly _theme$ = computed(() => {
return this.edgeless.std.get(ThemeProvider).theme$.value;
});
override type: GfxToolsFullOptionValue['type'] = 'connector';
override render() {
@@ -112,9 +112,6 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
mode,
this.onChange
);
const color = this.edgeless.std
.get(ThemeProvider)
.getColorValue(stroke, DEFAULT_CONNECTOR_COLOR);
return html`
<edgeless-slide-menu>
@@ -130,12 +127,14 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
<div class="submenu-divider"></div>
<edgeless-color-panel
class="one-way"
.value=${color}
.palettes=${STROKE_COLORS}
.value=${stroke}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc.awarenessStore.getFlag(
'enable_color_picker'
)}
@select=${(e: ColorEvent) => this.onChange({ stroke: e.detail })}
@select=${(e: ColorEvent) =>
this.onChange({ stroke: e.detail.value })}
></edgeless-color-panel>
</div>
</edgeless-slide-menu>
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* oxlint-disable @typescript-eslint/no-non-null-assertion */
import {
type MenuHandler,
popMenu,
@@ -25,6 +25,7 @@ import { debounce } from '@blocksuite/global/utils';
import { Slot } from '@blocksuite/store';
import { autoPlacement, offset } from '@floating-ui/dom';
import { ContextProvider } from '@lit/context';
import { computed } from '@preact/signals-core';
import { baseTheme, cssVar } from '@toeverything/theme';
import { css, html, nothing, unsafeCSS } from 'lit';
import { query, state } from 'lit/decorators.js';
@@ -222,6 +223,10 @@ export class EdgelessToolbarWidget extends WidgetComponent<
}
`;
private readonly _appTheme$ = computed(() => {
return this.std.get(ThemeProvider).app$.value;
});
private _moreQuickToolsMenu: MenuHandler | null = null;
private _moreQuickToolsMenuRef: HTMLElement | null = null;
@@ -625,9 +630,11 @@ export class EdgelessToolbarWidget extends WidgetComponent<
return nothing;
}
const appTheme = this.std.get(ThemeProvider).app$.value;
return html`
<div class="edgeless-toolbar-wrapper" data-app-theme=${appTheme}>
<div
class="edgeless-toolbar-wrapper"
data-app-theme=${this._appTheme$.value}
>
<div
class="edgeless-toolbar-toggle-control"
data-enable=${this._enableAutoHide}
@@ -166,7 +166,7 @@ export class EdgelessNoteSeniorButton extends EdgelessToolbarToolMixin(
}>
) => {
this._states.forEach(key => {
// eslint-disable-next-line eqeqeq
// oxlint-disable-next-line eqeqeq
if (props[key] != undefined) {
Object.assign(this, { [key]: props[key] });
}
@@ -60,7 +60,7 @@ export class EdgelessNoteToolButton extends QuickToolMixin(LitElement) {
}>
) => {
this._states.forEach(key => {
// eslint-disable-next-line eqeqeq
// oxlint-disable-next-line eqeqeq
if (props[key] != undefined) {
Object.assign(this, { [key]: props[key] });
}
@@ -3,27 +3,24 @@ import {
ScribbledStyleIcon,
} from '@blocksuite/affine-components/icons';
import {
DEFAULT_SHAPE_FILL_COLOR,
LIGHT_PALETTES,
MEDIUM_PALETTES,
SHAPE_FILL_COLORS,
DefaultTheme,
isTransparent,
type Palette,
type ShapeName,
ShapeStyle,
ShapeType,
StrokeColor,
} from '@blocksuite/affine-model';
import {
EditPropsStore,
ThemeProvider,
} from '@blocksuite/affine-shared/services';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
import type { Signal } from '@preact/signals-core';
import { computed, effect, signal } from '@preact/signals-core';
import { computed, effect, type Signal, signal } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import type { EdgelessRootBlockComponent } from '../../../edgeless-root-block.js';
import { type ColorEvent, isTransparent } from '../../panel/color-panel.js';
import type { ColorEvent } from '../../panel/color-panel.js';
import { ShapeComponentConfig } from './shape-menu-config.js';
export class EdgelessShapeMenu extends SignalWatcher(
@@ -76,20 +73,13 @@ export class EdgelessShapeMenu extends SignalWatcher(
};
});
private readonly _setFillColor = (fillColor: string) => {
const filled = !isTransparent(fillColor);
let strokeColor = fillColor; // white or black
if (filled) {
const index = LIGHT_PALETTES.findIndex(color => color === fillColor);
if (index !== -1) {
strokeColor = MEDIUM_PALETTES[index];
}
}
if (strokeColor.endsWith('transparent')) {
strokeColor = StrokeColor.Grey;
}
private readonly _setFillColor = ({ key, value }: Palette) => {
const filled = !isTransparent(value);
const fillColor = value;
const strokeColor = filled
? DefaultTheme.StrokeColorPalettes.find(palette => palette.key === key)
?.value
: DefaultTheme.StrokeColorMap.Grey;
const { shapeName } = this._props$.value;
this.edgeless.std
@@ -112,6 +102,10 @@ export class EdgelessShapeMenu extends SignalWatcher(
this.onChange(shapeName);
};
private readonly _theme$ = computed(() => {
return this.edgeless.std.get(ThemeProvider).theme$.value;
});
override connectedCallback(): void {
super.connectedCallback();
@@ -128,9 +122,6 @@ export class EdgelessShapeMenu extends SignalWatcher(
override render() {
const { fillColor, shapeStyle, shapeName } = this._props$.value;
const color = this.edgeless.std
.get(ThemeProvider)
.getColorValue(fillColor, DEFAULT_SHAPE_FILL_COLOR);
return html`
<edgeless-slide-menu>
@@ -179,8 +170,9 @@ export class EdgelessShapeMenu extends SignalWatcher(
<menu-divider .vertical=${true}></menu-divider>
<edgeless-color-panel
class="one-way"
.value=${color}
.palettes=${SHAPE_FILL_COLORS}
.value=${fillColor}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.FillColorPalettes}
.hasTransparent=${!this.edgeless.doc.awarenessStore.getFlag(
'enable_color_picker'
)}
@@ -1,5 +1,7 @@
import { STROKE_COLORS } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
import { computed } from '@preact/signals-core';
import { css, html, LitElement, nothing } from 'lit';
import { property } from 'lit/decorators.js';
@@ -15,6 +17,10 @@ export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
}
`;
private readonly _theme$ = computed(() => {
return this.edgeless.std.get(ThemeProvider).theme$.value;
});
override type: GfxToolsFullOptionValue['type'] = 'text';
override render() {
@@ -26,7 +32,8 @@ export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
<edgeless-color-panel
class="one-way"
.value=${this.color}
.palettes=${STROKE_COLORS}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorPalettes}
@select=${(e: ColorEvent) => this.onChange({ color: e.detail })}
></edgeless-color-panel>
</div>
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* oxlint-disable @typescript-eslint/no-non-null-assertion */
import type { PointerEventState } from '@blocksuite/block-std';
import { BaseTool, MouseButton } from '@blocksuite/block-std/gfx';
import { IS_MAC } from '@blocksuite/global/env';
@@ -3,11 +3,7 @@ import {
type SurfaceBlockComponent,
} from '@blocksuite/affine-block-surface';
import type { ShapeElementModel, ShapeName } from '@blocksuite/affine-model';
import {
DEFAULT_SHAPE_FILL_COLOR,
DEFAULT_SHAPE_STROKE_COLOR,
getShapeType,
} from '@blocksuite/affine-model';
import { DefaultTheme, getShapeType } from '@blocksuite/affine-model';
import {
EditPropsStore,
TelemetryProvider,
@@ -202,10 +198,14 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
options.stroke = this.std
.get(ThemeProvider)
.getColorValue(attributes.strokeColor, DEFAULT_SHAPE_STROKE_COLOR, true);
.getColorValue(
attributes.strokeColor,
DefaultTheme.shapeStrokeColor,
true
);
options.fill = this.std
.get(ThemeProvider)
.getColorValue(attributes.fillColor, DEFAULT_SHAPE_FILL_COLOR, true);
.getColorValue(attributes.fillColor, DefaultTheme.shapeFillColor, true);
switch (attributes.strokeStyle!) {
case 'dash':
@@ -1,9 +1,7 @@
import {
Black,
DEFAULT_ROUGHNESS,
LineWidth,
StrokeStyle,
White,
} from '@blocksuite/affine-model';
export const NOTE_OVERLAY_OFFSET_X = 6;
@@ -48,10 +46,6 @@ export const SurfaceColor = '#6046FE';
export const NoteColor = '#1E96EB';
export const BlendColor = '#7D91FF';
export const SHAPE_TEXT_COLOR_PURE_WHITE = White;
export const SHAPE_TEXT_COLOR_PURE_BLACK = Black;
export const SHAPE_FILL_COLOR_BLACK = Black;
export const AI_CHAT_BLOCK_MIN_WIDTH = 260;
export const AI_CHAT_BLOCK_MIN_HEIGHT = 160;
export const AI_CHAT_BLOCK_MAX_WIDTH = 320;
@@ -6,9 +6,7 @@ import {
} from '@blocksuite/affine-block-surface';
import {
type Color,
DEFAULT_NOTE_BACKGROUND_COLOR,
DEFAULT_SHAPE_FILL_COLOR,
DEFAULT_SHAPE_STROKE_COLOR,
DefaultTheme,
shapeMethods,
type ShapeStyle,
} from '@blocksuite/affine-model';
@@ -286,10 +284,10 @@ export class ShapeOverlay extends ToolOverlay {
const { shapeStyle, fillColor, strokeColor } = style;
const fill = this.gfx.std
.get(ThemeProvider)
.getColorValue(fillColor, DEFAULT_SHAPE_FILL_COLOR, true);
.getColorValue(fillColor, DefaultTheme.shapeFillColor, true);
const stroke = this.gfx.std
.get(ThemeProvider)
.getColorValue(strokeColor, DEFAULT_SHAPE_STROKE_COLOR, true);
.getColorValue(strokeColor, DefaultTheme.shapeStrokeColor, true);
options.fill = fill;
options.stroke = stroke;
@@ -356,7 +354,7 @@ export class NoteOverlay extends ToolOverlay {
this.globalAlpha = 0;
this.backgroundColor = gfx.std
.get(ThemeProvider)
.getColorValue(background, DEFAULT_NOTE_BACKGROUND_COLOR, true);
.getColorValue(background, DefaultTheme.noteBackgrounColor, true);
this.disposables.add(
effect(() => {
// when change note child type, update overlay text
@@ -4,10 +4,14 @@ import type {
BrushProps,
ColorScheme,
} from '@blocksuite/affine-model';
import { LineWidth, PALETTES } from '@blocksuite/affine-model';
import {
DefaultTheme,
LineWidth,
resolveColor,
} from '@blocksuite/affine-model';
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement, nothing } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { property, query } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
@@ -17,7 +21,6 @@ import {
packColorsWithColorScheme,
} from '../../edgeless/components/color-picker/utils.js';
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
import { GET_DEFAULT_LINE_COLOR } from '../../edgeless/components/panel/color-panel.js';
import type { LineWidthEvent } from '../../edgeless/components/panel/line-width-panel.js';
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
@@ -25,13 +28,13 @@ function getMostCommonColor(
elements: BrushElementModel[],
colorScheme: ColorScheme
): string {
const colors = countBy(elements, (ele: BrushElementModel) => {
return typeof ele.color === 'object'
? (ele.color[colorScheme] ?? ele.color.normal ?? null)
: ele.color;
});
const colors = countBy(elements, (ele: BrushElementModel) =>
resolveColor(ele.color, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : GET_DEFAULT_LINE_COLOR(colorScheme);
return max
? (max[0] as string)
: resolveColor(DefaultTheme.black, colorScheme);
}
function getMostCommonSize(elements: BrushElementModel[]): LineWidth {
@@ -45,26 +48,29 @@ function notEqual<K extends keyof BrushProps>(key: K, value: BrushProps[K]) {
}
export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
private readonly _setBrushColor = ({ detail: color }: ColorEvent) => {
private readonly _setBrushColor = ({ detail }: ColorEvent) => {
const color = detail.value;
this._setBrushProp('color', color);
this._selectedColor = color;
};
private readonly _setLineWidth = ({ detail: lineWidth }: LineWidthEvent) => {
this._setBrushProp('lineWidth', lineWidth);
this._selectedSize = lineWidth;
};
pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') {
this.elements.forEach(ele =>
this.crud.updateElement(ele.id, packColor('color', { ...event.detail }))
);
pickColor = (e: PickColorEvent) => {
const field = 'color';
if (e.type === 'pick') {
const color = e.detail.value;
this.elements.forEach(ele => {
const props = packColor(field, color);
this.crud.updateElement(ele.id, props);
});
return;
}
this.elements.forEach(ele =>
ele[event.type === 'start' ? 'stash' : 'pop']('color')
ele[e.type === 'start' ? 'stash' : 'pop'](field)
);
};
@@ -72,17 +78,6 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
return this.edgeless.doc;
}
get selectedColor() {
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
return (
this._selectedColor ?? getMostCommonColor(this.elements, colorScheme)
);
}
get selectedSize() {
return this._selectedSize ?? getMostCommonSize(this.elements);
}
get service() {
return this.edgeless.service;
}
@@ -110,7 +105,8 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
override render() {
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
const elements = this.elements;
const { selectedSize, selectedColor } = this;
const selectedColor = getMostCommonColor(elements, colorScheme);
const selectedSize = getMostCommonSize(elements);
return html`
<edgeless-line-width-panel
@@ -138,7 +134,8 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -156,6 +153,8 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
>
<edgeless-color-panel
.value=${selectedColor}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setBrushColor}
>
</edgeless-color-panel>
@@ -165,12 +164,6 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
`;
}
@state()
private accessor _selectedColor: string | null = null;
@state()
private accessor _selectedSize: LineWidth | null = null;
@query('edgeless-color-picker-button.color')
accessor colorButton!: EdgelessColorPickerButton;
@@ -28,9 +28,10 @@ import {
ConnectorMode,
DEFAULT_FRONT_END_POINT_STYLE,
DEFAULT_REAR_END_POINT_STYLE,
DefaultTheme,
LineWidth,
PALETTES,
PointStyle,
resolveColor,
StrokeStyle,
} from '@blocksuite/affine-model';
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
@@ -48,10 +49,7 @@ import {
packColor,
packColorsWithColorScheme,
} from '../../edgeless/components/color-picker/utils.js';
import {
type ColorEvent,
GET_DEFAULT_LINE_COLOR,
} from '../../edgeless/components/panel/color-panel.js';
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
import {
type LineStyleEvent,
LineStylesPanel,
@@ -62,14 +60,14 @@ import { mountConnectorLabelEditor } from '../../edgeless/utils/text.js';
function getMostCommonColor(
elements: ConnectorElementModel[],
colorScheme: ColorScheme
): string | null {
const colors = countBy(elements, (ele: ConnectorElementModel) => {
return typeof ele.stroke === 'object'
? (ele.stroke[colorScheme] ?? ele.stroke.normal ?? null)
: ele.stroke;
});
): string {
const colors = countBy(elements, (ele: ConnectorElementModel) =>
resolveColor(ele.stroke, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : null;
return max
? (max[0] as string)
: resolveColor(DefaultTheme.connectorColor, colorScheme);
}
function getMostCommonMode(
@@ -88,10 +86,10 @@ function getMostCommonLineWidth(elements: ConnectorElementModel[]): LineWidth {
export function getMostCommonLineStyle(
elements: ConnectorElementModel[]
): StrokeStyle | null {
): StrokeStyle {
const sizes = countBy(elements, ele => ele.strokeStyle);
const max = maxBy(Object.entries(sizes), ([_k, count]) => count);
return max ? (max[0] as StrokeStyle) : null;
return max ? (max[0] as StrokeStyle) : StrokeStyle.Solid;
}
function getMostCommonRough(elements: ConnectorElementModel[]): boolean {
@@ -112,15 +110,16 @@ function getMostCommonRough(elements: ConnectorElementModel[]): boolean {
function getMostCommonEndpointStyle(
elements: ConnectorElementModel[],
endpoint: ConnectorEndpoint
): PointStyle | null {
endpoint: ConnectorEndpoint,
fallback: PointStyle
): PointStyle {
const field =
endpoint === ConnectorEndpoint.Front
? 'frontEndpointStyle'
: 'rearEndpointStyle';
const modes = countBy(elements, ele => ele[field]);
const max = maxBy(Object.entries(modes), ([_k, count]) => count);
return max ? (max[0] as PointStyle) : null;
return max ? (max[0] as PointStyle) : fallback;
}
function notEqual<
@@ -227,19 +226,33 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') {
this.elements.forEach(ele =>
this.crud.updateElement(
ele.id,
packColor('stroke', { ...event.detail })
)
);
private readonly _setConnectorColor = (e: ColorEvent) => {
const stroke = e.detail.value;
this._setConnectorProp('stroke', stroke);
};
private readonly _setConnectorStroke = ({ type, value }: LineStyleEvent) => {
if (type === 'size') {
this._setConnectorStrokeWidth(value);
return;
}
this._setConnectorStrokeStyle(value);
};
pickColor = (e: PickColorEvent) => {
const field = 'stroke';
if (e.type === 'pick') {
const color = e.detail.value;
this.elements.forEach(ele => {
const props = packColor(field, color);
this.crud.updateElement(ele.id, props);
});
return;
}
this.elements.forEach(ele =>
ele[event.type === 'start' ? 'stash' : 'pop']('stroke')
ele[e.type === 'start' ? 'stash' : 'pop'](field)
);
};
@@ -276,10 +289,6 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
);
}
private _setConnectorColor(stroke: string) {
this._setConnectorProp('stroke', stroke);
}
private _setConnectorMode(mode: ConnectorMode) {
this._setConnectorProp('mode', mode);
}
@@ -310,14 +319,6 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
this._setConnectorProp('rough', rough);
}
private _setConnectorStroke({ type, value }: LineStyleEvent) {
if (type === 'size') {
this._setConnectorStrokeWidth(value);
return;
}
this._setConnectorStrokeStyle(value);
}
private _setConnectorStrokeStyle(strokeStyle: StrokeStyle) {
this._setConnectorProp('strokeStyle', strokeStyle);
}
@@ -339,20 +340,21 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
override render() {
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
const elements = this.elements;
const selectedColor =
getMostCommonColor(elements, colorScheme) ??
GET_DEFAULT_LINE_COLOR(colorScheme);
const selectedColor = getMostCommonColor(elements, colorScheme);
const selectedMode = getMostCommonMode(elements);
const selectedLineSize = getMostCommonLineWidth(elements) ?? LineWidth.Four;
const selectedLineSize = getMostCommonLineWidth(elements);
const selectedRough = getMostCommonRough(elements);
const selectedLineStyle =
getMostCommonLineStyle(elements) ?? StrokeStyle.Solid;
const selectedStartPointStyle =
getMostCommonEndpointStyle(elements, ConnectorEndpoint.Front) ??
DEFAULT_FRONT_END_POINT_STYLE;
const selectedEndPointStyle =
getMostCommonEndpointStyle(elements, ConnectorEndpoint.Rear) ??
DEFAULT_REAR_END_POINT_STYLE;
const selectedLineStyle = getMostCommonLineStyle(elements);
const selectedStartPointStyle = getMostCommonEndpointStyle(
elements,
ConnectorEndpoint.Front,
DEFAULT_FRONT_END_POINT_STYLE
);
const selectedEndPointStyle = getMostCommonEndpointStyle(
elements,
ConnectorEndpoint.Rear,
DEFAULT_REAR_END_POINT_STYLE
);
return join(
[
@@ -373,7 +375,8 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${true}
>
<div
@@ -389,7 +392,7 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
${LineStylesPanel({
selectedLineSize: selectedLineSize,
selectedLineStyle: selectedLineStyle,
onClick: (e: LineStyleEvent) => this._setConnectorStroke(e),
onClick: this._setConnectorStroke,
})}
</div>
<editor-toolbar-separator
@@ -414,13 +417,12 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
`}
>
<stroke-style-panel
.theme=${colorScheme}
.strokeWidth=${selectedLineSize}
.strokeStyle=${selectedLineStyle}
.strokeColor=${selectedColor}
.setStrokeStyle=${(e: LineStyleEvent) =>
this._setConnectorStroke(e)}
.setStrokeColor=${(e: ColorEvent) =>
this._setConnectorColor(e.detail)}
.setStrokeStyle=${this._setConnectorStroke}
.setStrokeColor=${this._setConnectorColor}
>
</stroke-style-panel>
</editor-menu-button>
@@ -9,9 +9,10 @@ import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import {
type ColorScheme,
DEFAULT_NOTE_HEIGHT,
DefaultTheme,
type FrameBlockModel,
NoteDisplayMode,
PALETTES,
resolveColor,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { GfxExtensionIdentifier } from '@blocksuite/block-std/gfx';
@@ -41,14 +42,12 @@ import { mountFrameTitleEditor } from '../../edgeless/utils/text.js';
function getMostCommonColor(
elements: FrameBlockModel[],
colorScheme: ColorScheme
): string | null {
const colors = countBy(elements, (ele: FrameBlockModel) => {
return typeof ele.background === 'object'
? (ele.background[colorScheme] ?? ele.background.normal ?? null)
: ele.background;
});
): string {
const colors = countBy(elements, (ele: FrameBlockModel) =>
resolveColor(ele.background, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : null;
return max ? (max[0] as string) : 'transparent';
}
export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
@@ -56,19 +55,27 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') {
this.frames.forEach(ele =>
this.crud.updateElement(
ele.id,
packColor('background', { ...event.detail })
)
);
private readonly _setFrameBackground = (e: ColorEvent) => {
const background = e.detail.value;
this.frames.forEach(frame => {
this.crud.updateElement(frame.id, { background });
});
};
pickColor = (e: PickColorEvent) => {
const field = 'background';
if (e.type === 'pick') {
const color = e.detail.value;
this.frames.forEach(ele => {
const props = packColor(field, color);
this.crud.updateElement(ele.id, props);
});
return;
}
this.frames.forEach(ele =>
ele[event.type === 'start' ? 'stash' : 'pop']('background')
ele[e.type === 'start' ? 'stash' : 'pop'](field)
);
};
@@ -119,18 +126,12 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
toast(this.edgeless.host, 'Frame has been inserted into doc');
}
private _setFrameBackground(color: string) {
this.frames.forEach(frame => {
this.crud.updateElement(frame.id, { background: color });
});
}
protected override render() {
const { frames } = this;
const len = frames.length;
const onlyOne = len === 1;
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
const background = getMostCommonColor(frames, colorScheme) ?? 'transparent';
const background = getMostCommonColor(frames, colorScheme);
return join(
[
@@ -203,7 +204,8 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
.color=${background}
.colors=${colors}
.colorType=${type}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -224,8 +226,9 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
>
<edgeless-color-panel
.value=${background}
.palettes=${PALETTES}
@select=${(e: ColorEvent) => this._setFrameBackground(e.detail)}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setFrameBackground}
>
</edgeless-color-panel>
</editor-menu-button>
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* oxlint-disable @typescript-eslint/no-non-null-assertion */
import {
MindmapBalanceLayoutIcon,
MindmapLeftLayoutIcon,
@@ -14,10 +14,10 @@ import {
} from '@blocksuite/affine-components/toolbar';
import {
type ColorScheme,
DEFAULT_NOTE_BACKGROUND_COLOR,
NOTE_BACKGROUND_PALETTES,
DefaultTheme,
type NoteBlockModel,
NoteDisplayMode,
resolveColor,
type StrokeStyle,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
@@ -43,7 +43,6 @@ import {
packColor,
packColorsWithColorScheme,
} from '../../edgeless/components/color-picker/utils.js';
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
import {
type LineStyleEvent,
LineStylesPanel,
@@ -68,14 +67,14 @@ const DisplayModeMap = {
function getMostCommonBackground(
elements: NoteBlockModel[],
colorScheme: ColorScheme
): string | null {
const colors = countBy(elements, (ele: NoteBlockModel) => {
return typeof ele.background === 'object'
? (ele.background[colorScheme] ?? ele.background.normal ?? null)
: ele.background;
});
): string {
const colors = countBy(elements, (ele: NoteBlockModel) =>
resolveColor(ele.background, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : null;
return max
? (max[0] as string)
: resolveColor(DefaultTheme.noteBackgrounColor, colorScheme);
}
export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
@@ -83,6 +82,12 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private readonly _setBackground = (background: string) => {
this.notes.forEach(element => {
this.crud.updateElement(element.id, { background });
});
};
private readonly _setBorderRadius = (borderRadius: number) => {
this.notes.forEach(note => {
const props = {
@@ -112,18 +117,19 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
});
};
pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') {
pickColor = (e: PickColorEvent) => {
const field = 'background';
if (e.type === 'pick') {
const color = e.detail.value;
this.notes.forEach(element => {
const props = packColor('background', { ...event.detail });
const props = packColor(field, color);
this.crud.updateElement(element.id, props);
});
return;
}
this.notes.forEach(ele =>
ele[event.type === 'start' ? 'stash' : 'pop']('background')
);
this.notes.forEach(ele => ele[e.type === 'start' ? 'stash' : 'pop'](field));
};
private get _advancedVisibilityEnabled() {
@@ -145,12 +151,6 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this.edgeless.slots.toggleNoteSlicer.emit();
}
private _setBackground(background: string) {
this.notes.forEach(element => {
this.crud.updateElement(element.id, { background });
});
}
private _setCollapse() {
this.notes.forEach(note => {
const { collapse, collapsedHeight } = note.edgeless;
@@ -262,9 +262,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
const { shadowType, borderRadius, borderSize, borderStyle } =
edgeless.style;
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
const background =
getMostCommonBackground(this.notes, colorScheme) ??
DEFAULT_NOTE_BACKGROUND_COLOR;
const background = getMostCommonBackground(this.notes, colorScheme);
const { collapse } = edgeless;
const scale = edgeless.scale ?? 1;
@@ -317,9 +315,11 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
.label=${'Background'}
.pick=${this.pickColor}
.color=${background}
.colorPanelClass=${'small'}
.colorType=${type}
.colors=${colors}
.palettes=${NOTE_BACKGROUND_PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.NoteBackgroundColorPalettes}
>
</edgeless-color-picker-button>
`;
@@ -339,9 +339,11 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
`}
>
<edgeless-color-panel
class="small"
.value=${background}
.options=${NOTE_BACKGROUND_PALETTES}
@select=${(e: ColorEvent) => this._setBackground(e.detail)}
.theme=${colorScheme}
.palettes=${DefaultTheme.NoteBackgroundColorPalettes}
@select=${this._setBackground}
>
</edgeless-color-panel>
</editor-menu-button>
@@ -8,20 +8,21 @@ import {
} from '@blocksuite/affine-components/icons';
import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import type {
Color,
ColorScheme,
ShapeElementModel,
ShapeProps,
} from '@blocksuite/affine-model';
import {
DEFAULT_SHAPE_FILL_COLOR,
DEFAULT_SHAPE_STROKE_COLOR,
DefaultTheme,
FontFamily,
getShapeName,
getShapeRadius,
getShapeType,
isTransparent,
LineWidth,
MindmapElementModel,
PALETTES,
resolveColor,
ShapeStyle,
StrokeStyle,
} from '@blocksuite/affine-model';
@@ -33,6 +34,7 @@ import { choose } from 'lit/directives/choose.js';
import { join } from 'lit/directives/join.js';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
import isEqual from 'lodash.isequal';
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
import type { PickColorEvent } from '../../edgeless/components/color-picker/types.js';
@@ -40,11 +42,7 @@ import {
packColor,
packColorsWithColorScheme,
} from '../../edgeless/components/color-picker/utils.js';
import {
type ColorEvent,
GET_DEFAULT_LINE_COLOR,
isTransparent,
} from '../../edgeless/components/panel/color-panel.js';
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
import {
type LineStyleEvent,
LineStylesPanel,
@@ -52,11 +50,6 @@ import {
import type { EdgelessShapePanel } from '../../edgeless/components/panel/shape-panel.js';
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
import type { ShapeToolOption } from '../../edgeless/gfx-tool/shape-tool.js';
import {
SHAPE_FILL_COLOR_BLACK,
SHAPE_TEXT_COLOR_PURE_BLACK,
SHAPE_TEXT_COLOR_PURE_WHITE,
} from '../../edgeless/utils/consts.js';
import { mountShapeTextEditor } from '../../edgeless/utils/text.js';
const changeShapeButtonStyles = [
@@ -88,62 +81,56 @@ const changeShapeButtonStyles = [
function getMostCommonFillColor(
elements: ShapeElementModel[],
colorScheme: ColorScheme
): string | null {
const colors = countBy(elements, (ele: ShapeElementModel) => {
if (ele.filled) {
return typeof ele.fillColor === 'object'
? (ele.fillColor[colorScheme] ?? ele.fillColor.normal ?? null)
: ele.fillColor;
}
return 'transparent';
});
): string {
const colors = countBy(elements, (ele: ShapeElementModel) =>
ele.filled ? resolveColor(ele.fillColor, colorScheme) : 'transparent'
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : null;
return max
? (max[0] as string)
: resolveColor(DefaultTheme.shapeFillColor, colorScheme);
}
function getMostCommonStrokeColor(
elements: ShapeElementModel[],
colorScheme: ColorScheme
): string | null {
const colors = countBy(elements, (ele: ShapeElementModel) => {
return typeof ele.strokeColor === 'object'
? (ele.strokeColor[colorScheme] ?? ele.strokeColor.normal ?? null)
: ele.strokeColor;
});
): string {
const colors = countBy(elements, (ele: ShapeElementModel) =>
resolveColor(ele.strokeColor, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : null;
return max
? (max[0] as string)
: resolveColor(DefaultTheme.shapeStrokeColor, colorScheme);
}
function getMostCommonShape(
elements: ShapeElementModel[]
): ShapeToolOption['shapeName'] | null {
const shapeTypes = countBy(elements, (ele: ShapeElementModel) => {
return getShapeName(ele.shapeType, ele.radius);
});
const shapeTypes = countBy(elements, (ele: ShapeElementModel) =>
getShapeName(ele.shapeType, ele.radius)
);
const max = maxBy(Object.entries(shapeTypes), ([_k, count]) => count);
return max ? (max[0] as ShapeToolOption['shapeName']) : null;
}
function getMostCommonLineSize(elements: ShapeElementModel[]): LineWidth {
const sizes = countBy(elements, (ele: ShapeElementModel) => {
return ele.strokeWidth;
});
const sizes = countBy(elements, (ele: ShapeElementModel) => ele.strokeWidth);
const max = maxBy(Object.entries(sizes), ([_k, count]) => count);
return max ? (Number(max[0]) as LineWidth) : LineWidth.Four;
}
function getMostCommonLineStyle(
elements: ShapeElementModel[]
): StrokeStyle | null {
function getMostCommonLineStyle(elements: ShapeElementModel[]): StrokeStyle {
const sizes = countBy(elements, (ele: ShapeElementModel) => ele.strokeStyle);
const max = maxBy(Object.entries(sizes), ([_k, count]) => count);
return max ? (max[0] as StrokeStyle) : null;
return max ? (max[0] as StrokeStyle) : StrokeStyle.Solid;
}
function getMostCommonShapeStyle(elements: ShapeElementModel[]): ShapeStyle {
const roughnesses = countBy(elements, (ele: ShapeElementModel) => {
return ele.shapeStyle;
});
const roughnesses = countBy(
elements,
(ele: ShapeElementModel) => ele.shapeStyle
);
const max = maxBy(Object.entries(roughnesses), ([_k, count]) => count);
return max ? (max[0] as ShapeStyle) : ShapeStyle.Scribbled;
}
@@ -151,6 +138,32 @@ function getMostCommonShapeStyle(elements: ShapeElementModel[]): ShapeStyle {
export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
static override styles = [changeShapeButtonStyles];
private readonly _setShapeFillColor = (e: ColorEvent) => {
const fillColor = e.detail.value;
const filled = !isTransparent(fillColor);
const color = this._getTextColor(fillColor, filled);
this.elements.forEach(ele =>
this.crud.updateElement(ele.id, { filled, fillColor, color })
);
};
private readonly _setShapeStrokeColor = (e: ColorEvent) => {
const strokeColor = e.detail.value;
this.elements.forEach(ele =>
this.crud.updateElement(ele.id, { strokeColor })
);
};
private readonly _setShapeStyles = ({ type, value }: LineStyleEvent) => {
if (type === 'size') {
this._setShapeStrokeWidth(value);
return;
}
if (type === 'lineStyle') {
this._setShapeStrokeStyle(value);
}
};
get service() {
return this.edgeless.service;
}
@@ -159,58 +172,24 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
#pickColor<K extends keyof Pick<ShapeProps, 'fillColor' | 'strokeColor'>>(
key: K
) {
return (event: PickColorEvent) => {
if (event.type === 'pick') {
this.elements.forEach(ele => {
const props = packColor(key, { ...event.detail });
// If `filled` can be set separately, this logic can be removed
if (key === 'fillColor' && !ele.filled) {
Object.assign(props, { filled: true });
}
this.crud.updateElement(ele.id, props);
});
return;
}
this.elements.forEach(ele =>
ele[event.type === 'start' ? 'stash' : 'pop'](key)
);
};
}
private _addText() {
mountShapeTextEditor(this.elements[0], this.edgeless);
}
private _getTextColor(fillColor: string) {
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
private _getTextColor(fillColor: Color, isNotTransparent = false) {
// When the shape is filled with black color, the text color should be white.
// When the shape is transparent, the text color should be set according to the theme.
// Otherwise, the text color should be black.
const textColor = isTransparent(fillColor)
? GET_DEFAULT_LINE_COLOR(colorScheme)
: fillColor === SHAPE_FILL_COLOR_BLACK
? SHAPE_TEXT_COLOR_PURE_WHITE
: SHAPE_TEXT_COLOR_PURE_BLACK;
return textColor;
}
if (isNotTransparent) {
if (isEqual(fillColor, DefaultTheme.black)) {
return DefaultTheme.white;
} else if (isEqual(fillColor, DefaultTheme.white)) {
return DefaultTheme.black;
}
}
private _setShapeFillColor(fillColor: string) {
const filled = !isTransparent(fillColor);
const color = this._getTextColor(fillColor);
this.elements.forEach(ele =>
this.crud.updateElement(ele.id, { filled, fillColor, color })
);
}
private _setShapeStrokeColor(strokeColor: string) {
this.elements.forEach(ele =>
this.crud.updateElement(ele.id, { strokeColor })
);
return DefaultTheme.black;
}
private _setShapeStrokeStyle(strokeStyle: StrokeStyle) {
@@ -234,16 +213,6 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
});
}
private _setShapeStyles({ type, value }: LineStyleEvent) {
if (type === 'size') {
this._setShapeStrokeWidth(value);
return;
}
if (type === 'lineStyle') {
this._setShapeStrokeStyle(value);
}
}
private _showAddButtonOrTextMenu() {
if (this.elements.length === 1 && !this.elements[0].text) {
return 'button';
@@ -270,20 +239,38 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
);
}
pickColor<K extends keyof Pick<ShapeProps, 'fillColor' | 'strokeColor'>>(
field: K
) {
return (e: PickColorEvent) => {
if (e.type === 'pick') {
const color = e.detail.value;
this.elements.forEach(ele => {
const props = packColor(field, color);
// If `filled` can be set separately, this logic can be removed
if (field === 'fillColor' && !ele.filled) {
Object.assign(props, { filled: true });
}
this.crud.updateElement(ele.id, props);
});
return;
}
this.elements.forEach(ele =>
ele[e.type === 'start' ? 'stash' : 'pop'](field)
);
};
}
override render() {
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
const elements = this.elements;
const selectedShape = getMostCommonShape(elements);
const selectedFillColor =
getMostCommonFillColor(elements, colorScheme) ?? DEFAULT_SHAPE_FILL_COLOR;
const selectedStrokeColor =
getMostCommonStrokeColor(elements, colorScheme) ??
DEFAULT_SHAPE_STROKE_COLOR;
const selectedLineSize = getMostCommonLineSize(elements) ?? LineWidth.Four;
const selectedLineStyle =
getMostCommonLineStyle(elements) ?? StrokeStyle.Solid;
const selectedShapeStyle =
getMostCommonShapeStyle(elements) ?? ShapeStyle.Scribbled;
const selectedFillColor = getMostCommonFillColor(elements, colorScheme);
const selectedStrokeColor = getMostCommonStrokeColor(elements, colorScheme);
const selectedLineSize = getMostCommonLineSize(elements);
const selectedLineStyle = getMostCommonLineStyle(elements);
const selectedShapeStyle = getMostCommonShapeStyle(elements);
return join(
[
@@ -340,11 +327,12 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
<edgeless-color-picker-button
class="fill-color"
.label=${'Fill color'}
.pick=${this.#pickColor('fillColor')}
.pick=${this.pickColor('fillColor')}
.color=${selectedFillColor}
.colors=${colors}
.colorType=${type}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -367,8 +355,9 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
role="listbox"
aria-label="Fill colors"
.value=${selectedFillColor}
.palettes=${PALETTES}
@select=${(e: ColorEvent) => this._setShapeFillColor(e.detail)}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setShapeFillColor}
>
</edgeless-color-panel>
</editor-menu-button>
@@ -388,11 +377,12 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
<edgeless-color-picker-button
class="border-style"
.label=${'Border style'}
.pick=${this.#pickColor('strokeColor')}
.pick=${this.pickColor('strokeColor')}
.color=${selectedStrokeColor}
.colors=${colors}
.colorType=${type}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${true}
>
<div
@@ -408,8 +398,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
${LineStylesPanel({
selectedLineSize: selectedLineSize,
selectedLineStyle: selectedLineStyle,
onClick: (e: LineStyleEvent) => this._setShapeStyles(e),
lineStyles: [StrokeStyle.Solid, StrokeStyle.Dash],
onClick: this._setShapeStyles,
})}
</div>
<editor-toolbar-separator
@@ -435,14 +424,13 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
`}
>
<stroke-style-panel
.theme=${colorScheme}
.hollowCircle=${true}
.strokeWidth=${selectedLineSize}
.strokeStyle=${selectedLineStyle}
.strokeColor=${selectedStrokeColor}
.setStrokeStyle=${(e: LineStyleEvent) =>
this._setShapeStyles(e)}
.setStrokeColor=${(e: ColorEvent) =>
this._setShapeStrokeColor(e.detail)}
.setStrokeStyle=${this._setShapeStyles}
.setStrokeColor=${this._setShapeStrokeColor}
>
</stroke-style-panel>
</editor-menu-button>
@@ -14,11 +14,12 @@ import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import {
type ColorScheme,
ConnectorElementModel,
DefaultTheme,
EdgelessTextBlockModel,
FontFamily,
FontStyle,
FontWeight,
PALETTES,
resolveColor,
ShapeElementModel,
TextAlign,
TextElementModel,
@@ -44,10 +45,7 @@ import {
packColor,
packColorsWithColorScheme,
} from '../../edgeless/components/color-picker/utils.js';
import {
type ColorEvent,
GET_DEFAULT_LINE_COLOR,
} from '../../edgeless/components/panel/color-panel.js';
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
const FONT_SIZE_LIST = [
@@ -121,12 +119,12 @@ function getMostCommonColor(
const colors = countBy(elements, (ele: BlockSuite.EdgelessTextModelType) => {
const color =
ele instanceof ConnectorElementModel ? ele.labelStyle.color : ele.color;
return typeof color === 'object'
? (color[colorScheme] ?? color.normal ?? null)
: color;
return resolveColor(color, colorScheme);
});
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : GET_DEFAULT_LINE_COLOR(colorScheme);
return max
? (max[0] as string)
: resolveColor(DefaultTheme.textColor, colorScheme);
}
function getMostCommonFontFamily(elements: BlockSuite.EdgelessTextModelType[]) {
@@ -229,7 +227,8 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
});
};
private readonly _setTextColor = ({ detail: color }: ColorEvent) => {
private readonly _setTextColor = (e: ColorEvent) => {
const color = e.detail.value;
const props = { color };
this.elements.forEach(element => {
this.crud.updateElement(element.id, buildProps(element, props));
@@ -307,10 +306,11 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
// no need to update the bound of edgeless text block, which updates itself using ResizeObserver
};
pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') {
pickColor = (e: PickColorEvent) => {
if (e.type === 'pick') {
const color = e.detail.value;
this.elements.forEach(element => {
const props = packColor('color', { ...event.detail });
const props = packColor('color', color);
this.crud.updateElement(element.id, buildProps(element, props));
this._updateElementBound(element);
});
@@ -319,7 +319,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
const key = this.elementType === 'connector' ? 'labelStyle' : 'color';
this.elements.forEach(ele => {
ele[event.type === 'start' ? 'stash' : 'pop'](key as 'color');
ele[e.type === 'start' ? 'stash' : 'pop'](key as 'color');
});
};
@@ -391,7 +391,8 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -412,7 +413,8 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
>
<edgeless-color-panel
.value=${selectedColor}
.palettes=${PALETTES}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setTextColor}
></edgeless-color-panel>
</editor-menu-button>
@@ -1,4 +1,8 @@
import { ColorScheme, FrameBlockModel } from '@blocksuite/affine-model';
import {
ColorScheme,
FrameBlockModel,
isTransparent,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import {
type BlockStdScope,
@@ -19,7 +23,6 @@ import { LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';
import { parseStringToRgba } from '../../edgeless/components/color-picker/utils.js';
import { isTransparent } from '../../edgeless/components/panel/color-panel.js';
import type { EdgelessRootService } from '../../edgeless/index.js';
import { frameTitleStyle, frameTitleStyleVars } from './styles.js';