refactor(editor): edgeless shape toolbar config extension (#10821)
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
export * from '@blocksuite/affine-components/edgeless-shape-color-picker';
|
||||||
@@ -27,6 +27,7 @@ import { effects as componentDatePickerEffects } from '@blocksuite/affine-compon
|
|||||||
import { effects as componentDropIndicatorEffects } from '@blocksuite/affine-components/drop-indicator';
|
import { effects as componentDropIndicatorEffects } from '@blocksuite/affine-components/drop-indicator';
|
||||||
import { effects as componentEdgelessLineStylesEffects } from '@blocksuite/affine-components/edgeless-line-styles-panel';
|
import { effects as componentEdgelessLineStylesEffects } from '@blocksuite/affine-components/edgeless-line-styles-panel';
|
||||||
import { effects as componentEdgelessLineWidthEffects } from '@blocksuite/affine-components/edgeless-line-width-panel';
|
import { effects as componentEdgelessLineWidthEffects } from '@blocksuite/affine-components/edgeless-line-width-panel';
|
||||||
|
import { effects as componentEdgelessShapeColorPickerEffects } from '@blocksuite/affine-components/edgeless-shape-color-picker';
|
||||||
import { effects as componentEmbedCardModalEffects } from '@blocksuite/affine-components/embed-card-modal';
|
import { effects as componentEmbedCardModalEffects } from '@blocksuite/affine-components/embed-card-modal';
|
||||||
import { FilterableListComponent } from '@blocksuite/affine-components/filterable-list';
|
import { FilterableListComponent } from '@blocksuite/affine-components/filterable-list';
|
||||||
import { effects as componentHighlightDropdownMenuEffects } from '@blocksuite/affine-components/highlight-dropdown-menu';
|
import { effects as componentHighlightDropdownMenuEffects } from '@blocksuite/affine-components/highlight-dropdown-menu';
|
||||||
@@ -153,6 +154,7 @@ export function effects() {
|
|||||||
componentSizeDropdownMenuEffects();
|
componentSizeDropdownMenuEffects();
|
||||||
componentEdgelessLineWidthEffects();
|
componentEdgelessLineWidthEffects();
|
||||||
componentEdgelessLineStylesEffects();
|
componentEdgelessLineStylesEffects();
|
||||||
|
componentEdgelessShapeColorPickerEffects();
|
||||||
|
|
||||||
widgetScrollAnchoringEffects();
|
widgetScrollAnchoringEffects();
|
||||||
widgetFrameTitleEffects();
|
widgetFrameTitleEffects();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { StyleGeneralIcon, StyleScribbleIcon } from '@blocksuite/icons/lit';
|
|||||||
import { computed, effect, type Signal, signal } from '@preact/signals-core';
|
import { computed, effect, type Signal, signal } from '@preact/signals-core';
|
||||||
import { css, html, LitElement } from 'lit';
|
import { css, html, LitElement } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
import { when } from 'lit/directives/when.js';
|
||||||
|
|
||||||
import type { EdgelessRootBlockComponent } from '../../../edgeless-root-block.js';
|
import type { EdgelessRootBlockComponent } from '../../../edgeless-root-block.js';
|
||||||
import { ShapeComponentConfig } from './shape-menu-config.js';
|
import { ShapeComponentConfig } from './shape-menu-config.js';
|
||||||
@@ -125,6 +126,11 @@ export class EdgelessShapeMenu extends SignalWatcher(
|
|||||||
return html`
|
return html`
|
||||||
<edgeless-slide-menu>
|
<edgeless-slide-menu>
|
||||||
<div class="menu-content">
|
<div class="menu-content">
|
||||||
|
${
|
||||||
|
// TODO(@fundon): add a flag
|
||||||
|
when(
|
||||||
|
false,
|
||||||
|
() => html`
|
||||||
<div class="shape-style-container">
|
<div class="shape-style-container">
|
||||||
<edgeless-tool-icon-button
|
<edgeless-tool-icon-button
|
||||||
.tooltip=${'General'}
|
.tooltip=${'General'}
|
||||||
@@ -150,6 +156,9 @@ export class EdgelessShapeMenu extends SignalWatcher(
|
|||||||
</edgeless-tool-icon-button>
|
</edgeless-tool-icon-button>
|
||||||
</div>
|
</div>
|
||||||
<menu-divider .vertical=${true}></menu-divider>
|
<menu-divider .vertical=${true}></menu-divider>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
<div class="shape-type-container">
|
<div class="shape-type-container">
|
||||||
${ShapeComponentConfig.map(
|
${ShapeComponentConfig.map(
|
||||||
({ name, generalIcon, scribbledIcon, tooltip }) => {
|
({ name, generalIcon, scribbledIcon, tooltip }) => {
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export const builtinConnectorToolbarConfig = {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
id: 'a.stroke-color',
|
id: 'a.stroke-color',
|
||||||
tooltip: 'Stroke style',
|
|
||||||
content(ctx) {
|
content(ctx) {
|
||||||
const models = ctx.getSurfaceModelsByType(ConnectorElementModel);
|
const models = ctx.getSurfaceModelsByType(ConnectorElementModel);
|
||||||
if (!models.length) return null;
|
if (!models.length) return null;
|
||||||
@@ -358,9 +357,9 @@ export const builtinConnectorToolbarConfig = {
|
|||||||
|
|
||||||
// No need to adjust element bounds
|
// No need to adjust element bounds
|
||||||
if (props['textAlign']) {
|
if (props['textAlign']) {
|
||||||
ctx.std.get(EdgelessCRUDIdentifier).updateElement(model.id, {
|
ctx.std
|
||||||
labelStyle,
|
.get(EdgelessCRUDIdentifier)
|
||||||
});
|
.updateElement(model.id, { labelStyle });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import {
|
|||||||
MindmapElementModel,
|
MindmapElementModel,
|
||||||
MindmapStyle,
|
MindmapStyle,
|
||||||
} from '@blocksuite/affine-model';
|
} from '@blocksuite/affine-model';
|
||||||
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
import type {
|
||||||
|
ToolbarContext,
|
||||||
|
ToolbarModuleConfig,
|
||||||
|
} from '@blocksuite/affine-shared/services';
|
||||||
import { getMostCommonValue } from '@blocksuite/affine-shared/utils';
|
import { getMostCommonValue } from '@blocksuite/affine-shared/utils';
|
||||||
import { RadiantIcon, RightLayoutIcon, StyleIcon } from '@blocksuite/icons/lit';
|
import { RadiantIcon, RightLayoutIcon, StyleIcon } from '@blocksuite/icons/lit';
|
||||||
|
|
||||||
@@ -55,14 +58,10 @@ const MINDMAP_LAYOUT_LIST = [
|
|||||||
},
|
},
|
||||||
] as const satisfies MenuItem<LayoutType>[];
|
] as const satisfies MenuItem<LayoutType>[];
|
||||||
|
|
||||||
export const builtinMindmapToolbarConfig = {
|
export const createMindmapStyleActionMenu = (
|
||||||
actions: [
|
ctx: ToolbarContext,
|
||||||
{
|
models: MindmapElementModel[]
|
||||||
id: 'a.style',
|
) => {
|
||||||
content(ctx) {
|
|
||||||
const models = ctx.getSurfaceModelsByType(MindmapElementModel);
|
|
||||||
if (!models.length) return null;
|
|
||||||
|
|
||||||
const style = getMostCommonValue(models, 'style') ?? MindmapStyle.ONE;
|
const style = getMostCommonValue(models, 'style') ?? MindmapStyle.ONE;
|
||||||
const onPick = (style: MindmapStyle) => {
|
const onPick = (style: MindmapStyle) => {
|
||||||
for (const model of models) {
|
for (const model of models) {
|
||||||
@@ -78,14 +77,12 @@ export const builtinMindmapToolbarConfig = {
|
|||||||
currentValue: style,
|
currentValue: style,
|
||||||
onPick,
|
onPick,
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'b.layout',
|
|
||||||
content(ctx) {
|
|
||||||
const models = ctx.getSurfaceModelsByType(MindmapElementModel);
|
|
||||||
if (!models.length) return null;
|
|
||||||
|
|
||||||
|
export const createMindmapLayoutActionMenu = (
|
||||||
|
ctx: ToolbarContext,
|
||||||
|
models: MindmapElementModel[]
|
||||||
|
) => {
|
||||||
const layoutType =
|
const layoutType =
|
||||||
getMostCommonValue(models, 'layoutType') ?? LayoutType.BALANCE;
|
getMostCommonValue(models, 'layoutType') ?? LayoutType.BALANCE;
|
||||||
const onPick = (layoutType: LayoutType) => {
|
const onPick = (layoutType: LayoutType) => {
|
||||||
@@ -102,6 +99,26 @@ export const builtinMindmapToolbarConfig = {
|
|||||||
currentValue: layoutType,
|
currentValue: layoutType,
|
||||||
onPick,
|
onPick,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const builtinMindmapToolbarConfig = {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'a.style',
|
||||||
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(MindmapElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
return createMindmapStyleActionMenu(ctx, models);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b.layout',
|
||||||
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(MindmapElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
return createMindmapLayoutActionMenu(ctx, models);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,39 +1,388 @@
|
|||||||
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
|
|
||||||
import {
|
import {
|
||||||
AddTextIcon,
|
EdgelessCRUDIdentifier,
|
||||||
ShapeIcon,
|
normalizeShapeBound,
|
||||||
StyleGeneralIcon,
|
} from '@blocksuite/affine-block-surface';
|
||||||
} from '@blocksuite/icons/lit';
|
import {
|
||||||
|
packColor,
|
||||||
|
type PickColorEvent,
|
||||||
|
} from '@blocksuite/affine-components/color-picker';
|
||||||
|
import type { LineDetailType } from '@blocksuite/affine-components/edgeless-line-styles-panel';
|
||||||
|
import {
|
||||||
|
type Color,
|
||||||
|
DefaultTheme,
|
||||||
|
FontFamily,
|
||||||
|
getShapeName,
|
||||||
|
getShapeRadius,
|
||||||
|
getShapeType,
|
||||||
|
isTransparent,
|
||||||
|
LineWidth,
|
||||||
|
MindmapElementModel,
|
||||||
|
resolveColor,
|
||||||
|
ShapeElementModel,
|
||||||
|
type ShapeName,
|
||||||
|
ShapeStyle,
|
||||||
|
ShapeType,
|
||||||
|
StrokeStyle,
|
||||||
|
} from '@blocksuite/affine-model';
|
||||||
|
import {
|
||||||
|
FeatureFlagService,
|
||||||
|
type ToolbarGenericAction,
|
||||||
|
type ToolbarModuleConfig,
|
||||||
|
} from '@blocksuite/affine-shared/services';
|
||||||
|
import { getMostCommonValue } from '@blocksuite/affine-shared/utils';
|
||||||
|
import { Bound } from '@blocksuite/global/gfx';
|
||||||
|
import { AddTextIcon, ShapeIcon } from '@blocksuite/icons/lit';
|
||||||
|
import { html } from 'lit';
|
||||||
|
import isEqual from 'lodash-es/isEqual';
|
||||||
|
|
||||||
|
import { EdgelessRootBlockComponent, type ShapeToolOption } from '../..';
|
||||||
|
import { ShapeComponentConfig } from '../../components/toolbar/shape/shape-menu-config';
|
||||||
|
import { mountShapeTextEditor } from '../../utils/text';
|
||||||
|
import { LINE_STYLE_LIST } from './consts';
|
||||||
|
import {
|
||||||
|
createMindmapLayoutActionMenu,
|
||||||
|
createMindmapStyleActionMenu,
|
||||||
|
} from './mindmap';
|
||||||
|
import { createTextActions } from './text-common';
|
||||||
|
import { renderMenu } from './utils';
|
||||||
|
|
||||||
export const builtinShapeToolbarConfig = {
|
export const builtinShapeToolbarConfig = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
id: 'a.switch-type',
|
id: 'a.mindmap-style',
|
||||||
|
when(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
return models.some(hasGrouped);
|
||||||
|
},
|
||||||
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
let mindmaps = models
|
||||||
|
.map(model => model.group)
|
||||||
|
.filter(model => ctx.matchModel(model, MindmapElementModel));
|
||||||
|
if (!mindmaps.length) return null;
|
||||||
|
|
||||||
|
// Not displayed when there is both a normal shape and a mindmap shape.
|
||||||
|
if (models.length !== mindmaps.length) return null;
|
||||||
|
|
||||||
|
mindmaps = Array.from(new Set(mindmaps));
|
||||||
|
|
||||||
|
return createMindmapStyleActionMenu(ctx, mindmaps);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b.mindmap-layout',
|
||||||
|
when(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
return models.some(hasGrouped);
|
||||||
|
},
|
||||||
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
let mindmaps = models
|
||||||
|
.map(model => model.group)
|
||||||
|
.filter(model => ctx.matchModel(model, MindmapElementModel));
|
||||||
|
if (!mindmaps.length) return null;
|
||||||
|
|
||||||
|
// Not displayed when there is both a normal shape and a mindmap shape.
|
||||||
|
if (models.length !== mindmaps.length) return null;
|
||||||
|
|
||||||
|
mindmaps = Array.from(new Set(mindmaps));
|
||||||
|
|
||||||
|
// It's a sub node.
|
||||||
|
if (models.length === 1 && mindmaps[0].tree.element !== models[0])
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return createMindmapLayoutActionMenu(ctx, mindmaps);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c.switch-type',
|
||||||
|
when(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
return models.length > 0 && models.every(model => !hasGrouped(model));
|
||||||
|
},
|
||||||
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
const shapeName =
|
||||||
|
getMostCommonValue<ShapeToolOption, 'shapeName'>(
|
||||||
|
models.map(model => ({
|
||||||
|
shapeName: getShapeName(model.shapeType, model.radius),
|
||||||
|
})),
|
||||||
|
'shapeName'
|
||||||
|
) ?? ShapeType.Rect;
|
||||||
|
|
||||||
|
const onPick = (shapeName: ShapeName) => {
|
||||||
|
const shapeType = getShapeType(shapeName);
|
||||||
|
const radius = getShapeRadius(shapeName);
|
||||||
|
|
||||||
|
ctx.std.store.captureSync();
|
||||||
|
|
||||||
|
for (const model of models) {
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, { shapeType, radius });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderMenu({
|
||||||
icon: ShapeIcon(),
|
icon: ShapeIcon(),
|
||||||
tooltip: 'Switch type',
|
label: 'Switch type',
|
||||||
run() {},
|
items: ShapeComponentConfig.map(item => ({
|
||||||
|
key: item.tooltip,
|
||||||
|
value: item.name,
|
||||||
|
// TODO(@fundon): should add a feature flag to switch style
|
||||||
|
icon: item.generalIcon,
|
||||||
|
disabled: item.disabled,
|
||||||
|
})),
|
||||||
|
currentValue: shapeName,
|
||||||
|
onPick,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'b.style',
|
id: 'd.style',
|
||||||
icon: StyleGeneralIcon(),
|
// TODO(@fundon): should add a feature flag
|
||||||
tooltip: 'Style',
|
when: false,
|
||||||
run() {},
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
const field = 'shapeStyle';
|
||||||
|
const shapeStyle =
|
||||||
|
getMostCommonValue(models, field) ?? ShapeStyle.General;
|
||||||
|
const onPick = (value: boolean) => {
|
||||||
|
const shapeStyle = value ? ShapeStyle.Scribbled : ShapeStyle.General;
|
||||||
|
const fontFamily = value ? FontFamily.Kalam : FontFamily.Inter;
|
||||||
|
|
||||||
|
for (const model of models) {
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, { shapeStyle, fontFamily });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderMenu({
|
||||||
|
label: 'Style',
|
||||||
|
items: LINE_STYLE_LIST,
|
||||||
|
currentValue: shapeStyle === ShapeStyle.Scribbled,
|
||||||
|
onPick,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'c.fill-color',
|
id: 'e.color',
|
||||||
label: 'Fill color',
|
when(ctx) {
|
||||||
run() {},
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
return models.length > 0 && models.every(model => !hasGrouped(model));
|
||||||
|
},
|
||||||
|
content(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
if (!models.length) return null;
|
||||||
|
|
||||||
|
const enableCustomColor = ctx.std
|
||||||
|
.get(FeatureFlagService)
|
||||||
|
.getFlag('enable_color_picker');
|
||||||
|
const theme = ctx.themeProvider.edgelessTheme;
|
||||||
|
|
||||||
|
const firstModel = models[0];
|
||||||
|
const originalFillColor = firstModel.fillColor;
|
||||||
|
const originalStrokeColor = firstModel.strokeColor;
|
||||||
|
|
||||||
|
const mapped = models.map(
|
||||||
|
({ filled, fillColor, strokeColor, strokeWidth, strokeStyle }) => ({
|
||||||
|
fillColor: filled
|
||||||
|
? resolveColor(fillColor, theme)
|
||||||
|
: DefaultTheme.transparent,
|
||||||
|
strokeColor: resolveColor(strokeColor, theme),
|
||||||
|
strokeWidth,
|
||||||
|
strokeStyle,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const fillColor =
|
||||||
|
getMostCommonValue(mapped, 'fillColor') ??
|
||||||
|
resolveColor(DefaultTheme.shapeFillColor, theme);
|
||||||
|
const strokeColor =
|
||||||
|
getMostCommonValue(mapped, 'strokeColor') ??
|
||||||
|
resolveColor(DefaultTheme.shapeStrokeColor, theme);
|
||||||
|
const strokeWidth =
|
||||||
|
getMostCommonValue(mapped, 'strokeWidth') ?? LineWidth.Four;
|
||||||
|
const strokeStyle =
|
||||||
|
getMostCommonValue(mapped, 'strokeStyle') ?? StrokeStyle.Solid;
|
||||||
|
|
||||||
|
const onPickFillColor = (e: CustomEvent<PickColorEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const d = e.detail;
|
||||||
|
|
||||||
|
const field = 'fillColor';
|
||||||
|
|
||||||
|
if (d.type === 'pick') {
|
||||||
|
const value = d.detail.value;
|
||||||
|
const filled = isTransparent(value);
|
||||||
|
for (const model of models) {
|
||||||
|
const props = packColor(field, value);
|
||||||
|
// If `filled` can be set separately, this logic can be removed
|
||||||
|
if (field && !model.filled) {
|
||||||
|
const color = getTextColor(value, filled);
|
||||||
|
Object.assign(props, { filled, color });
|
||||||
|
}
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, props);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const model of models) {
|
||||||
|
model[d.type === 'start' ? 'stash' : 'pop'](field);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onPickStrokeColor = (e: CustomEvent<PickColorEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const d = e.detail;
|
||||||
|
|
||||||
|
const field = 'strokeColor';
|
||||||
|
|
||||||
|
if (d.type === 'pick') {
|
||||||
|
const value = d.detail.value;
|
||||||
|
for (const model of models) {
|
||||||
|
const props = packColor(field, value);
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, props);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const model of models) {
|
||||||
|
model[d.type === 'start' ? 'stash' : 'pop'](field);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onPickStrokeStyle = (e: CustomEvent<LineDetailType>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const { type, value } = e.detail;
|
||||||
|
|
||||||
|
if (type === 'size') {
|
||||||
|
const strokeWidth = value;
|
||||||
|
for (const model of models) {
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, { strokeWidth });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strokeStyle = value;
|
||||||
|
for (const model of models) {
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, { strokeStyle });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<edgeless-shape-color-picker
|
||||||
|
@pickFillColor=${onPickFillColor}
|
||||||
|
@pickStrokeColor=${onPickStrokeColor}
|
||||||
|
@pickStrokeStyle=${onPickStrokeStyle}
|
||||||
|
.payload=${{
|
||||||
|
fillColor,
|
||||||
|
strokeColor,
|
||||||
|
strokeWidth,
|
||||||
|
strokeStyle,
|
||||||
|
originalFillColor,
|
||||||
|
originalStrokeColor,
|
||||||
|
theme,
|
||||||
|
enableCustomColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</edgeless-shape-color-picker>
|
||||||
|
`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'd.border-style',
|
id: 'f.text',
|
||||||
label: 'Border style',
|
|
||||||
run() {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'e.text',
|
|
||||||
icon: AddTextIcon(),
|
icon: AddTextIcon(),
|
||||||
tooltip: 'Show add button or text menu',
|
when(ctx) {
|
||||||
run() {},
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
return models.length === 1 && !hasGrouped(models[0]) && !models[0].text;
|
||||||
},
|
},
|
||||||
|
run(ctx) {
|
||||||
|
const model = ctx.getCurrentModelByType(ShapeElementModel);
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
const rootModel = ctx.store.root;
|
||||||
|
if (!rootModel) return;
|
||||||
|
|
||||||
|
// TODO(@fundon): it should be simple
|
||||||
|
const edgeless = ctx.view.getBlock(rootModel.id);
|
||||||
|
if (!ctx.matchBlock(edgeless, EdgelessRootBlockComponent)) {
|
||||||
|
console.error('edgeless view is not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mountShapeTextEditor(model, edgeless);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// id: `g.text`
|
||||||
|
...createTextActions(ShapeElementModel, 'shape', (ctx, model, props) => {
|
||||||
|
// No need to adjust element bounds
|
||||||
|
if (props['textAlign']) {
|
||||||
|
ctx.std.get(EdgelessCRUDIdentifier).updateElement(model.id, props);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xywh = normalizeShapeBound(
|
||||||
|
model,
|
||||||
|
Bound.fromXYWH(model.deserializedXYWH)
|
||||||
|
).serialize();
|
||||||
|
|
||||||
|
ctx.std
|
||||||
|
.get(EdgelessCRUDIdentifier)
|
||||||
|
.updateElement(model.id, { ...props, xywh });
|
||||||
|
}).map<ToolbarGenericAction>(action => ({
|
||||||
|
...action,
|
||||||
|
id: `g.text-${action.id}`,
|
||||||
|
when(ctx) {
|
||||||
|
const models = ctx.getSurfaceModelsByType(ShapeElementModel);
|
||||||
|
return (
|
||||||
|
models.length > 0 &&
|
||||||
|
models.every(model => !hasGrouped(model) && model.text)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
when: ctx => ctx.getSurfaceModelsByType(ShapeElementModel).length > 0,
|
||||||
} as const satisfies ToolbarModuleConfig;
|
} as const satisfies ToolbarModuleConfig;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
function getTextColor(fillColor: Color, isNotTransparent = false) {
|
||||||
|
if (isNotTransparent) {
|
||||||
|
if (isEqual(fillColor, DefaultTheme.black)) {
|
||||||
|
return DefaultTheme.white;
|
||||||
|
} else if (isEqual(fillColor, DefaultTheme.white)) {
|
||||||
|
return DefaultTheme.black;
|
||||||
|
} else if (isEqual(fillColor, DefaultTheme.pureBlack)) {
|
||||||
|
return DefaultTheme.pureWhite;
|
||||||
|
} else if (isEqual(fillColor, DefaultTheme.pureWhite)) {
|
||||||
|
return DefaultTheme.pureBlack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// aka `DefaultTheme.pureBlack`
|
||||||
|
return DefaultTheme.shapeTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasGrouped(model: ShapeElementModel) {
|
||||||
|
return model.group instanceof MindmapElementModel;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export type MenuItem<T> = {
|
|||||||
key?: string;
|
key?: string;
|
||||||
value: T;
|
value: T;
|
||||||
icon?: TemplateResult;
|
icon?: TemplateResult;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Menu<T> = {
|
export type Menu<T> = {
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ export function renderMenuItems<T>(
|
|||||||
return repeat(
|
return repeat(
|
||||||
items,
|
items,
|
||||||
item => item.value,
|
item => item.value,
|
||||||
({ key, value, icon }) => html`
|
({ key, value, icon, disabled }) => html`
|
||||||
<editor-icon-button
|
<editor-icon-button
|
||||||
aria-label="${ifDefined(key)}"
|
aria-label="${ifDefined(key)}"
|
||||||
|
.disabled=${ifDefined(disabled)}
|
||||||
.tooltip="${ifDefined(key)}"
|
.tooltip="${ifDefined(key)}"
|
||||||
.active="${currentValue === value}"
|
.active="${currentValue === value}"
|
||||||
.activeMode="${'background'}"
|
.activeMode="${'background'}"
|
||||||
|
|||||||
@@ -68,7 +68,8 @@
|
|||||||
"./tooltip-content-with-shortcut": "./src/tooltip-content-with-shortcut/index.ts",
|
"./tooltip-content-with-shortcut": "./src/tooltip-content-with-shortcut/index.ts",
|
||||||
"./size-dropdown-menu": "./src/size-dropdown-menu/index.ts",
|
"./size-dropdown-menu": "./src/size-dropdown-menu/index.ts",
|
||||||
"./edgeless-line-width-panel": "./src/edgeless-line-width-panel/index.ts",
|
"./edgeless-line-width-panel": "./src/edgeless-line-width-panel/index.ts",
|
||||||
"./edgeless-line-styles-panel": "./src/edgeless-line-styles-panel/index.ts"
|
"./edgeless-line-styles-panel": "./src/edgeless-line-styles-panel/index.ts",
|
||||||
|
"./edgeless-shape-color-picker": "./src/edgeless-shape-color-picker/index.ts"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import { when } from 'lit-html/directives/when.js';
|
|||||||
import type { EditorMenuButton } from '../toolbar/menu-button';
|
import type { EditorMenuButton } from '../toolbar/menu-button';
|
||||||
import type { PickColorEvent } from './types';
|
import type { PickColorEvent } from './types';
|
||||||
import {
|
import {
|
||||||
|
calcCustomButtonStyle,
|
||||||
keepColor,
|
keepColor,
|
||||||
packColorsWithColorScheme,
|
packColorsWith,
|
||||||
preprocessColor,
|
preprocessColor,
|
||||||
rgbaToHex8,
|
rgbaToHex8,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
@@ -35,39 +36,20 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
get colorWithoutAlpha() {
|
get colorWithoutAlpha() {
|
||||||
return this.isCSSVariable ? this.color : keepColor(this.color);
|
return keepColor(
|
||||||
}
|
this.color.startsWith('--')
|
||||||
|
? rgbaToHex8(
|
||||||
get customButtonStyle() {
|
|
||||||
let b = 'transparent';
|
|
||||||
let c = 'transparent';
|
|
||||||
|
|
||||||
if (!this.isCustomColor) {
|
|
||||||
return { '--b': b, '--c': c };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isCSSVariable) {
|
|
||||||
if (!this.color.endsWith('transparent')) {
|
|
||||||
b = 'var(--affine-background-overlay-panel-color)';
|
|
||||||
c = keepColor(
|
|
||||||
rgbaToHex8(
|
|
||||||
preprocessColor(window.getComputedStyle(this))({
|
preprocessColor(window.getComputedStyle(this))({
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
value: this.color,
|
value: this.color,
|
||||||
}).rgba
|
}).rgba
|
||||||
)
|
)
|
||||||
|
: this.color
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
b = 'var(--affine-background-overlay-panel-color)';
|
|
||||||
c = keepColor(this.color);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { '--b': b, '--c': c };
|
get customButtonStyle() {
|
||||||
}
|
return calcCustomButtonStyle(this.color, this.isCustomColor, this);
|
||||||
|
|
||||||
get isCSSVariable() {
|
|
||||||
return this.color.startsWith('--');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCustomColor() {
|
get isCustomColor() {
|
||||||
@@ -160,7 +142,7 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
|
|||||||
[
|
[
|
||||||
'custom',
|
'custom',
|
||||||
() => {
|
() => {
|
||||||
const packed = packColorsWithColorScheme(
|
const packed = packColorsWith(
|
||||||
this.theme,
|
this.theme,
|
||||||
this.color,
|
this.color,
|
||||||
this.originalColor
|
this.originalColor
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export const packColor = (key: string, color: Color) => {
|
|||||||
* @param oldColor - The old color
|
* @param oldColor - The old color
|
||||||
* @returns A color array
|
* @returns A color array
|
||||||
*/
|
*/
|
||||||
export const packColorsWithColorScheme = (
|
export const packColorsWith = (
|
||||||
colorScheme: ColorScheme,
|
colorScheme: ColorScheme,
|
||||||
value: string,
|
value: string,
|
||||||
oldColor: Color
|
oldColor: Color
|
||||||
@@ -308,3 +308,35 @@ export const packColorsWithColorScheme = (
|
|||||||
|
|
||||||
return { type, colors };
|
return { type, colors };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const calcCustomButtonStyle = (
|
||||||
|
color: string,
|
||||||
|
isCustomColor: boolean,
|
||||||
|
ele: Element
|
||||||
|
) => {
|
||||||
|
let b = 'transparent';
|
||||||
|
let c = 'transparent';
|
||||||
|
|
||||||
|
if (!isCustomColor) {
|
||||||
|
return { '--b': b, '--c': c };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.startsWith('---')) {
|
||||||
|
if (!color.endsWith('transparent')) {
|
||||||
|
b = 'var(--affine-background-overlay-panel-color)';
|
||||||
|
c = keepColor(
|
||||||
|
rgbaToHex8(
|
||||||
|
preprocessColor(window.getComputedStyle(ele))({
|
||||||
|
type: 'normal',
|
||||||
|
value: color,
|
||||||
|
}).rgba
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b = 'var(--affine-background-overlay-panel-color)';
|
||||||
|
c = keepColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { '--b': b, '--c': c };
|
||||||
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class EdgelessLineStylesPanel extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<affine-edgeless-line-width-panel
|
<affine-edgeless-line-width-panel
|
||||||
?disabled=${lineStyle === StrokeStyle.None}
|
?disabled="${lineStyle === StrokeStyle.None}"
|
||||||
.selectedSize=${lineSize}
|
.selectedSize=${lineSize}
|
||||||
@select=${(e: CustomEvent<LineWidth>) => {
|
@select=${(e: CustomEvent<LineWidth>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
import {
|
||||||
|
type Color,
|
||||||
|
type ColorScheme,
|
||||||
|
DefaultTheme,
|
||||||
|
type LineWidth,
|
||||||
|
type Palette,
|
||||||
|
resolveColor,
|
||||||
|
type ShapeProps,
|
||||||
|
type StrokeStyle,
|
||||||
|
} from '@blocksuite/affine-model';
|
||||||
|
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||||
|
import {
|
||||||
|
type ColorEvent,
|
||||||
|
stopPropagation,
|
||||||
|
} from '@blocksuite/affine-shared/utils';
|
||||||
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
|
import { batch, signal } from '@preact/signals-core';
|
||||||
|
import { css, html, LitElement } from 'lit';
|
||||||
|
import { property, query } from 'lit/decorators.js';
|
||||||
|
import { choose } from 'lit-html/directives/choose.js';
|
||||||
|
import { repeat } from 'lit-html/directives/repeat.js';
|
||||||
|
import { styleMap } from 'lit-html/directives/style-map.js';
|
||||||
|
import { when } from 'lit-html/directives/when.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
calcCustomButtonStyle,
|
||||||
|
keepColor,
|
||||||
|
packColorsWith,
|
||||||
|
type PickColorEvent,
|
||||||
|
preprocessColor,
|
||||||
|
rgbaToHex8,
|
||||||
|
} from '../color-picker';
|
||||||
|
import type { LineDetailType } from '../edgeless-line-styles-panel';
|
||||||
|
import type { EditorMenuButton } from '../toolbar';
|
||||||
|
|
||||||
|
type TabType = 'normal' | 'custom';
|
||||||
|
|
||||||
|
type ColorType = Extract<keyof ShapeProps, 'fillColor' | 'strokeColor'>;
|
||||||
|
|
||||||
|
type PickerType = {
|
||||||
|
label: string;
|
||||||
|
type: ColorType;
|
||||||
|
value: string;
|
||||||
|
hollowCircle: boolean;
|
||||||
|
onPick: (e: ColorEvent) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EdgelessShapeColorPicker extends WithDisposable(
|
||||||
|
SignalWatcher(LitElement)
|
||||||
|
) {
|
||||||
|
static override styles = css`
|
||||||
|
.pickers {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-label {
|
||||||
|
color: ${unsafeCSSVarV2('text/secondary')};
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
tabType$ = signal<TabType>('normal');
|
||||||
|
|
||||||
|
colorType$ = signal<ColorType>('fillColor');
|
||||||
|
|
||||||
|
readonly #pickFillColor = (e: ColorEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent<PickColorEvent>('pickFillColor', {
|
||||||
|
detail: {
|
||||||
|
type: 'pick',
|
||||||
|
detail: e.detail,
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
cancelable: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly #pickStrokeColor = (e: ColorEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent<PickColorEvent>('pickStrokeColor', {
|
||||||
|
detail: {
|
||||||
|
type: 'pick',
|
||||||
|
detail: e.detail,
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
cancelable: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly #pickColor = (detail: PickColorEvent) => {
|
||||||
|
const type =
|
||||||
|
this.colorType$.peek() === 'fillColor'
|
||||||
|
? 'pickFillColor'
|
||||||
|
: 'pickStrokeColor';
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent<PickColorEvent>(type, {
|
||||||
|
detail,
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
cancelable: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly #pickStrokeStyle = (e: CustomEvent<LineDetailType>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('pickStrokeStyle', {
|
||||||
|
detail: e.detail,
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
cancelable: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
#calcCustomButtonStyle(color: string, isCustomColor: boolean) {
|
||||||
|
return calcCustomButtonStyle(color, isCustomColor, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#calcCustomButtonState(color: string, theme: ColorScheme) {
|
||||||
|
return !this.palettes
|
||||||
|
.map(({ value }) => resolveColor(value, theme))
|
||||||
|
.includes(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#switchToCustomWith(type: ColorType) {
|
||||||
|
batch(() => {
|
||||||
|
this.tabType$.value = 'custom';
|
||||||
|
this.colorType$.value = type;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get fillColorWithoutAlpha() {
|
||||||
|
const { fillColor } = this.payload;
|
||||||
|
return keepColor(
|
||||||
|
fillColor.startsWith('--')
|
||||||
|
? rgbaToHex8(
|
||||||
|
preprocessColor(window.getComputedStyle(this))({
|
||||||
|
type: 'normal',
|
||||||
|
value: fillColor,
|
||||||
|
}).rgba
|
||||||
|
)
|
||||||
|
: fillColor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override firstUpdated() {
|
||||||
|
this.disposables.addFromEvent(
|
||||||
|
this.menuButton,
|
||||||
|
'toggle',
|
||||||
|
(e: CustomEvent<boolean>) => {
|
||||||
|
const opened = e.detail;
|
||||||
|
if (!opened && this.tabType$.peek() === 'custom') {
|
||||||
|
this.tabType$.value = 'normal';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
const {
|
||||||
|
tabType$: { value: tabType },
|
||||||
|
colorType$: { value: colorType },
|
||||||
|
palettes,
|
||||||
|
fillColorWithoutAlpha,
|
||||||
|
payload: {
|
||||||
|
fillColor,
|
||||||
|
strokeColor,
|
||||||
|
strokeWidth,
|
||||||
|
strokeStyle,
|
||||||
|
originalFillColor,
|
||||||
|
originalStrokeColor,
|
||||||
|
theme,
|
||||||
|
enableCustomColor,
|
||||||
|
},
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<editor-menu-button
|
||||||
|
.contentPadding="${tabType === 'normal' ? '8px' : '0px'}"
|
||||||
|
@click=${stopPropagation}
|
||||||
|
.button=${html`
|
||||||
|
<editor-icon-button aria-label="Color" .tooltip="${'Color'}">
|
||||||
|
<edgeless-color-button
|
||||||
|
.color=${fillColorWithoutAlpha}
|
||||||
|
></edgeless-color-button>
|
||||||
|
</editor-icon-button>
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div class="pickers" data-orientation="vertical">
|
||||||
|
${choose(tabType, [
|
||||||
|
[
|
||||||
|
'normal',
|
||||||
|
() => {
|
||||||
|
return html`
|
||||||
|
${repeat(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Fill color',
|
||||||
|
type: 'fillColor',
|
||||||
|
value: fillColor,
|
||||||
|
hollowCircle: false,
|
||||||
|
onPick: this.#pickFillColor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Border color',
|
||||||
|
type: 'strokeColor',
|
||||||
|
value: strokeColor,
|
||||||
|
hollowCircle: true,
|
||||||
|
onPick: this.#pickStrokeColor,
|
||||||
|
},
|
||||||
|
] satisfies PickerType[],
|
||||||
|
item => item.type,
|
||||||
|
({ label, type, value, onPick, hollowCircle }) => html`
|
||||||
|
<div class="picker-label">${label}</div>
|
||||||
|
<edgeless-color-panel
|
||||||
|
role="listbox"
|
||||||
|
.hasTransparent=${false}
|
||||||
|
.hollowCircle=${hollowCircle}
|
||||||
|
.value=${value}
|
||||||
|
.theme=${theme}
|
||||||
|
.palettes=${palettes}
|
||||||
|
@select=${onPick}
|
||||||
|
>
|
||||||
|
${when(enableCustomColor, () => {
|
||||||
|
const isCustomColor = this.#calcCustomButtonState(
|
||||||
|
value,
|
||||||
|
theme
|
||||||
|
);
|
||||||
|
const styleInfo = this.#calcCustomButtonStyle(
|
||||||
|
value,
|
||||||
|
isCustomColor
|
||||||
|
);
|
||||||
|
return html`
|
||||||
|
<edgeless-color-custom-button
|
||||||
|
slot="custom"
|
||||||
|
style=${styleMap(styleInfo)}
|
||||||
|
?active=${isCustomColor}
|
||||||
|
@click=${() => this.#switchToCustomWith(type)}
|
||||||
|
></edgeless-color-custom-button>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</edgeless-color-panel>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<div class="picker-label">Border style</div>
|
||||||
|
<affine-edgeless-line-styles-panel
|
||||||
|
class="picker"
|
||||||
|
.lineSize=${strokeWidth}
|
||||||
|
.lineStyle=${strokeStyle}
|
||||||
|
@select=${this.#pickStrokeStyle}
|
||||||
|
></affine-edgeless-line-styles-panel>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'custom',
|
||||||
|
() => {
|
||||||
|
const isFillColor = colorType === 'fillColor';
|
||||||
|
const packed = packColorsWith(
|
||||||
|
theme,
|
||||||
|
isFillColor ? fillColor : strokeColor,
|
||||||
|
isFillColor ? originalFillColor : originalStrokeColor
|
||||||
|
);
|
||||||
|
const type = packed.type === 'palette' ? 'normal' : packed.type;
|
||||||
|
const modes = packed.colors.map(
|
||||||
|
preprocessColor(window.getComputedStyle(this))
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<edgeless-color-picker
|
||||||
|
class="custom"
|
||||||
|
.pick=${this.#pickColor}
|
||||||
|
.colors=${{ type, modes }}
|
||||||
|
></edgeless-color-picker>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
</editor-menu-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor payload!: {
|
||||||
|
fillColor: string;
|
||||||
|
strokeColor: string;
|
||||||
|
strokeWidth: LineWidth;
|
||||||
|
strokeStyle: StrokeStyle;
|
||||||
|
originalFillColor: Color;
|
||||||
|
originalStrokeColor: Color;
|
||||||
|
theme: ColorScheme;
|
||||||
|
enableCustomColor: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor palettes: Palette[] = DefaultTheme.Palettes;
|
||||||
|
|
||||||
|
@query('editor-menu-button')
|
||||||
|
accessor menuButton!: EditorMenuButton;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { EdgelessShapeColorPicker } from './color-picker';
|
||||||
|
|
||||||
|
export * from './color-picker';
|
||||||
|
|
||||||
|
export function effects() {
|
||||||
|
customElements.define(
|
||||||
|
'edgeless-shape-color-picker',
|
||||||
|
EdgelessShapeColorPicker
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user