diff --git a/blocksuite/affine/blocks/note/src/components/edgeless-note-shadow-dropdown-menu.ts b/blocksuite/affine/blocks/note/src/components/edgeless-note-shadow-dropdown-menu.ts deleted file mode 100644 index 80ca8850d7..0000000000 --- a/blocksuite/affine/blocks/note/src/components/edgeless-note-shadow-dropdown-menu.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { EditorChevronDown } from '@blocksuite/affine-components/toolbar'; -import { ColorScheme, NoteShadow } from '@blocksuite/affine-model'; -import { NoteShadowDuotoneIcon } from '@blocksuite/icons/lit'; -import { css, html, LitElement } from 'lit'; -import { property } from 'lit/decorators.js'; -import { repeat } from 'lit/directives/repeat.js'; -import { styleMap } from 'lit/directives/style-map.js'; - -import { NoteNoShadowIcon, NoteShadowSampleIcon } from './icons'; - -const SHADOWS = [ - { - type: NoteShadow.None, - styles: { - light: '', - dark: '', - }, - tooltip: 'No shadow', - }, - { - type: NoteShadow.Box, - styles: { - light: - '0px 0.2px 4.8px 0px rgba(66, 65, 73, 0.2), 0px 0px 1.6px 0px rgba(66, 65, 73, 0.2)', - dark: '0px 0.2px 6px 0px rgba(0, 0, 0, 0.44), 0px 0px 2px 0px rgba(0, 0, 0, 0.66)', - }, - tooltip: 'Box shadow', - }, - { - type: NoteShadow.Sticker, - styles: { - light: - '0px 9.6px 10.4px -4px rgba(66, 65, 73, 0.07), 0px 10.4px 7.2px -8px rgba(66, 65, 73, 0.22)', - dark: '0px 9.6px 10.4px -4px rgba(0, 0, 0, 0.66), 0px 10.4px 7.2px -8px rgba(0, 0, 0, 0.44)', - }, - tooltip: 'Sticker shadow', - }, - { - type: NoteShadow.Paper, - styles: { - light: - '0px 0px 0px 4px rgba(255, 255, 255, 1), 0px 1.2px 2.4px 4.8px rgba(66, 65, 73, 0.16)', - dark: '0px 1.2px 2.4px 4.8px rgba(0, 0, 0, 0.36), 0px 0px 0px 3.4px rgba(75, 75, 75, 1)', - }, - tooltip: 'Paper shadow', - }, - { - type: NoteShadow.Float, - styles: { - light: - '0px 5.2px 12px 0px rgba(66, 65, 73, 0.13), 0px 0px 0.4px 1px rgba(0, 0, 0, 0.06)', - dark: '0px 5.2px 12px 0px rgba(0, 0, 0, 0.66), 0px 0px 0.4px 1px rgba(0, 0, 0, 0.44)', - }, - tooltip: 'Floation shadow', - }, - { - type: NoteShadow.Film, - styles: { - light: - '0px 0px 0px 1.4px rgba(0, 0, 0, 1), 2.4px 2.4px 0px 1px rgba(0, 0, 0, 1)', - dark: '0px 0px 0px 1.4px rgba(178, 178, 178, 1), 2.4px 2.4px 0px 1px rgba(178, 178, 178, 1)', - }, - tooltip: 'Film shadow', - }, -]; - -export class EdgelessNoteShadowDropdownMenu extends LitElement { - static override styles = css` - :host { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - } - - .item { - padding: 8px; - border-radius: 4px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - } - - .item-icon { - display: flex; - justify-content: center; - align-items: center; - } - - .item-icon svg rect:first-of-type { - fill: var(--background); - } - - .item:hover { - background-color: var(--affine-hover-color); - } - - .item[data-selected] { - border: 1px solid var(--affine-brand-color); - } - `; - - select(value: NoteShadow) { - this.dispatchEvent(new CustomEvent('select', { detail: value })); - } - - override render() { - const { value, background, theme } = this; - const isDark = theme === ColorScheme.Dark; - - return html` - - ${NoteShadowDuotoneIcon()} ${EditorChevronDown} - - `} - > -
- ${repeat( - SHADOWS, - shadow => shadow.type, - ({ type, tooltip, styles: { dark, light } }, index) => - html`
this.select(type)} - > - - ${index === 0 ? NoteNoShadowIcon : NoteShadowSampleIcon} - -
` - )} -
-
- `; - } - - @property({ attribute: false }) - accessor background!: string; - - @property({ attribute: false }) - accessor theme!: ColorScheme; - - @property({ attribute: false }) - accessor value!: NoteShadow; -} - -declare global { - interface HTMLElementTagNameMap { - 'edgeless-note-shadow-dropdown-menu': EdgelessNoteShadowDropdownMenu; - } -} diff --git a/blocksuite/affine/blocks/note/src/components/edgeless-note-shadow-menu.ts b/blocksuite/affine/blocks/note/src/components/edgeless-note-shadow-menu.ts new file mode 100644 index 0000000000..b3bd03f43f --- /dev/null +++ b/blocksuite/affine/blocks/note/src/components/edgeless-note-shadow-menu.ts @@ -0,0 +1,206 @@ +import { ColorScheme, NoteShadow } from '@blocksuite/affine-model'; +import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; +import { css, html, LitElement, type PropertyValues } from 'lit'; +import { property } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { styleMap } from 'lit/directives/style-map.js'; + +import { NoteNoShadowIcon, NoteShadowSampleIcon } from './icons'; + +type Shadow = { + type: NoteShadow; + light: Parameters[0]; + dark: Parameters[0]; + style: Parameters[0]; + tooltip: string; +}; + +const SHADOWS: Shadow[] = [ + { + type: NoteShadow.None, + light: {}, + dark: {}, + style: {}, + tooltip: 'No shadow', + }, + { + type: NoteShadow.Box, + light: { + '--note-box-shadow-color-1': 'rgba(0, 0, 0, 0.14)', + '--note-box-shadow-color-2': 'rgba(0, 0, 0, 0.14)', + }, + dark: { + '--note-box-shadow-color-1': 'rgba(0, 0, 0, 0.30)', + '--note-box-shadow-color-2': 'rgba(0, 0, 0, 0.44)', + }, + style: { + boxShadow: + '0px 0.109px 2.621px var(--note-box-shadow-color-1), 0px 0px 0.874px var(--note-box-shadow-color-2)', + }, + tooltip: 'Box shadow', + }, + { + type: NoteShadow.Sticker, + light: { + '--note-sticker-shadow-color-1': 'rgba(0, 0, 0, 0.08)', + '--note-sticker-shadow-color-2': 'rgba(0, 0, 0, 0.10)', + }, + dark: { + '--note-sticker-shadow-color-1': 'rgba(0, 0, 0, 0.22)', + '--note-sticker-shadow-color-2': 'rgba(0, 0, 0, 0.52)', + }, + style: { + boxShadow: + '0px 5.243px 5.68px var(--note-sticker-shadow-color-1), 0px 5.68px 3.932px var(--note-sticker-shadow-color-2)', + }, + tooltip: 'Sticker shadow', + }, + { + type: NoteShadow.Paper, + light: { + '--note-paper-shadow-color-1': 'rgba(0, 0, 0, 0.14)', + '--note-paper-shadow-color-2': '#FFF', + }, + dark: { + '--note-paper-shadow-color-1': 'rgba(0, 0, 0, 0.30)', + '--note-paper-shadow-color-2': '#7A7A7A', + }, + style: { + border: '2px solid var(--note-paper-shadow-color-2)', + boxShadow: '0px 0.655px 1.311px var(--note-paper-shadow-color-1)', + }, + tooltip: 'Paper shadow', + }, + { + type: NoteShadow.Float, + light: { + '--note-float-shadow-color-1': 'rgba(0, 0, 0, 0.09)', + '--note-float-shadow-color-2': 'rgba(0, 0, 0, 0.10)', + }, + dark: { + '--note-float-shadow-color-1': 'rgba(0, 0, 0, 0.30)', + '--note-float-shadow-color-2': 'rgba(0, 0, 0, 0.44)', + }, + style: { + boxShadow: + '0px 2.84px 6.554px var(--note-float-shadow-color-1), 0px 0px 0.218px var(--note-float-shadow-color-2)', + }, + tooltip: 'Floating shadow', + }, + { + type: NoteShadow.Film, + light: { + '--note-film-shadow-color-1': '#000', + '--note-film-shadow-color-2': '#000', + }, + dark: { + '--note-film-shadow-color-1': '#7A7A7A', + '--note-film-shadow-color-2': '#7A7A7A', + }, + style: { + border: '1px solid var(--note-film-shadow-color-1)', + boxShadow: '2px 2px 0px var(--note-film-shadow-color-2)', + }, + tooltip: 'Film shadow', + }, +]; + +export class EdgelessNoteShadowMenu extends LitElement { + static override styles = css` + :host { + display: flex; + align-items: center; + justify-content: space-between; + } + + .item { + padding: 4.369px; + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + } + + .item-icon { + display: flex; + justify-content: center; + align-items: center; + } + + .item-icon svg { + width: 30px; + height: auto; + border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/blackBorder')}; + fill: ${unsafeCSSVarV2('layer/insideBorder/blackBorder')}; + } + + .item-icon svg rect:first-of-type { + fill: var(--background); + } + + .item:hover { + background-color: var(--affine-hover-color); + } + + .item[data-selected] { + border: 1px solid var(--affine-brand-color); + } + `; + + select(value: NoteShadow) { + this.dispatchEvent(new CustomEvent('select', { detail: value })); + } + + override willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has('background')) { + this.style.setProperty('--background', this.background); + } + } + + override render() { + const { value, theme } = this; + const isDark = theme === ColorScheme.Dark; + + return repeat( + SHADOWS, + shadow => shadow.type, + ({ type, tooltip, style, light, dark }, index) => + html`
this.select(type)} + > + + ${index === 0 ? NoteNoShadowIcon : NoteShadowSampleIcon} + +
` + ); + } + + @property({ attribute: false }) + accessor background!: string; + + @property({ attribute: false }) + accessor theme!: ColorScheme; + + @property({ attribute: false }) + accessor value!: NoteShadow; +} + +declare global { + interface HTMLElementTagNameMap { + 'edgeless-note-shadow-menu': EdgelessNoteShadowMenu; + } +} diff --git a/blocksuite/affine/blocks/note/src/components/edgeless-note-style-panel.ts b/blocksuite/affine/blocks/note/src/components/edgeless-note-style-panel.ts new file mode 100644 index 0000000000..2e4b23c3d3 --- /dev/null +++ b/blocksuite/affine/blocks/note/src/components/edgeless-note-style-panel.ts @@ -0,0 +1,400 @@ +import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface'; +import { + packColorsWith, + type PickColorEvent, + preprocessColor, +} from '@blocksuite/affine-components/color-picker'; +import type { LineDetailType } from '@blocksuite/affine-components/edgeless-line-styles-panel'; +import type { SliderSelectEvent } from '@blocksuite/affine-components/slider'; +import { + DefaultTheme, + NoteBlockModel, + type NoteProps, + type NoteShadow, + resolveColor, +} from '@blocksuite/affine-model'; +import { ThemeProvider } from '@blocksuite/affine-shared/services'; +import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; +import { + type ColorEvent, + getMostCommonResolvedValue, + stopPropagation, +} from '@blocksuite/affine-shared/utils'; +import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; +import { ArrowLeftSmallIcon, PaletteIcon } from '@blocksuite/icons/lit'; +import { BlockStdScope, PropTypes, requiredProperties } from '@blocksuite/std'; +import { css, html, LitElement } from 'lit'; +import { property, query, state } from 'lit/decorators.js'; +import { choose } from 'lit/directives/choose.js'; + +@requiredProperties({ + notes: PropTypes.arrayOf(model => model instanceof NoteBlockModel), + std: PropTypes.instanceOf(BlockStdScope), +}) +export class EdgelessNoteStylePanel extends SignalWatcher( + WithDisposable(LitElement) +) { + @property({ attribute: false }) + accessor notes!: NoteBlockModel[]; + + @property({ attribute: false }) + accessor std!: BlockStdScope; + + @query('.edgeless-note-style-panel') + private accessor _panel!: HTMLDivElement; + + @state() + accessor tabType: 'style' | 'customColor' = 'style'; + + static override styles = css` + .edgeless-note-style-panel { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 8px; + } + + .edgeless-note-style-section { + display: flex; + flex-direction: column; + align-items: stretch; + } + + .edgeless-note-style-section-title { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 4px; + height: 22px; + align-self: stretch; + color: ${unsafeCSSVarV2('text/secondary')}; + font-feature-settings: + 'liga' off, + 'clig' off; + + /* Client/sm */ + font-family: var(--affine-font-family); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } + + edgeless-line-styles-panel { + display: flex; + flex-direction: row; + gap: 8px; + } + + .edgeless-note-corner-radius-panel { + display: flex; + flex-direction: row; + align-items: stretch; + gap: 8px; + + affine-slider { + width: 168px; + } + + input { + border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; + border-radius: 4px; + text-indent: 4px; + box-sizing: border-box; + width: 88px; + color: ${unsafeCSSVarV2('text/placeholder')}; + } + + input:focus { + outline: none; + border-color: ${unsafeCSSVarV2('input/border/active')}; + color: ${unsafeCSSVarV2('text/primary')}; + } + } + + .edgeless-note-style-custom-color-panel { + display: flex; + flex-direction: column; + align-items: stretch; + } + + .edgeless-note-custom-color-picker { + padding-top: 0px; + } + `; + + private _styleChanged = false; + + private _beforeChange() { + if (!this._styleChanged) { + // record the history + this.std.store.captureSync(); + this._styleChanged = true; + } + } + + private get _theme() { + return this.std.get(ThemeProvider).edgeless$.value; + } + + private get _background() { + return ( + getMostCommonResolvedValue( + this.notes.map(model => model.props), + 'background', + background => resolveColor(background, this._theme) + ) ?? resolveColor(DefaultTheme.noteBackgrounColor, this._theme) + ); + } + + private get _originalBackground() { + return this.notes[0].props.background; + } + + private get _shadow() { + return this.notes[0].props.edgeless.style.shadowType; + } + + private get _borderSize() { + return this.notes[0].props.edgeless.style.borderSize; + } + + private get _borderStyle() { + return this.notes[0].props.edgeless.style.borderStyle; + } + + private get _borderRadius() { + return this.notes[0].props.edgeless.style.borderRadius; + } + + private readonly _switchToCustomColorTab = () => { + this.tabType = 'customColor'; + }; + + private readonly _switchToStyleTab = () => { + this.tabType = 'style'; + }; + + private readonly _selectColor = (e: ColorEvent) => { + this._beforeChange(); + const color = e.detail.value; + const crud = this.std.get(EdgelessCRUDIdentifier); + this.notes.forEach(note => { + crud.updateElement(note.id, { + background: color, + } satisfies Partial); + }); + }; + + private readonly _pickColor = (e: PickColorEvent) => { + console.log(e); + }; + + private readonly _selectShadow = (e: CustomEvent) => { + this._beforeChange(); + const shadowType = e.detail; + const crud = this.std.get(EdgelessCRUDIdentifier); + this.notes.forEach(note => { + crud.updateElement(note.id, { + edgeless: { + ...note.props.edgeless, + style: { + ...note.props.edgeless.style, + shadowType, + }, + }, + } satisfies Partial); + }); + }; + + private readonly _selectBorder = (e: CustomEvent) => { + this._beforeChange(); + + const { type, value } = e.detail; + const crud = this.std.get(EdgelessCRUDIdentifier); + if (type === 'size') { + const borderSize = value; + this.notes.forEach(note => { + const edgeless = note.props.edgeless; + crud.updateElement(note.id, { + edgeless: { + ...edgeless, + style: { + ...edgeless.style, + borderSize, + }, + }, + } satisfies Partial); + }); + } else { + const borderStyle = value; + this.notes.forEach(note => { + const edgeless = note.props.edgeless; + crud.updateElement(note.id, { + edgeless: { ...edgeless, style: { ...edgeless.style, borderStyle } }, + } satisfies Partial); + }); + } + }; + + private readonly _selectBorderRadius = ( + e: SliderSelectEvent | InputEvent + ) => { + this._beforeChange(); + + let borderRadius = this._borderRadius; + if (e instanceof InputEvent) { + const target = e.target as HTMLInputElement; + const value = parseInt(target.value); + if (isNaN(value)) { + return; + } + borderRadius = value; + } else { + borderRadius = e.detail.value; + } + const crud = this.std.get(EdgelessCRUDIdentifier); + this.notes.forEach(note => { + crud.updateElement(note.id, { + edgeless: { + ...note.props.edgeless, + style: { ...note.props.edgeless.style, borderRadius }, + }, + } satisfies Partial); + }); + }; + + private _renderStylePanel() { + return html`
+
+
Fill color
+ + + +
+
+
Shadow
+ +
+
+
Border
+ +
+
+
Corner Radius
+
+ + + + + +
+
+
`; + } + + private _renderCustomColorPanel() { + const packed = packColorsWith( + this._theme, + this._background, + this._originalBackground + ); + const type = packed.type === 'palette' ? 'normal' : packed.type; + const modes = packed.colors.map( + preprocessColor(window.getComputedStyle(this)) + ); + + return html`
+
+ + ${ArrowLeftSmallIcon()} + + Custom color +
+ +
`; + } + + override firstUpdated() { + this.disposables.addFromEvent(this._panel, 'click', e => { + e.stopPropagation(); + }); + } + + override render() { + return html` + + ${PaletteIcon()} + + `} + > + ${choose(this.tabType, [ + ['style', () => this._renderStylePanel()], + ['customColor', () => this._renderCustomColorPanel()], + ])} + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'edgeless-note-style-panel': EdgelessNoteStylePanel; + } +} diff --git a/blocksuite/affine/blocks/note/src/components/icons.ts b/blocksuite/affine/blocks/note/src/components/icons.ts index 42a224a69e..60a0966037 100644 --- a/blocksuite/affine/blocks/note/src/components/icons.ts +++ b/blocksuite/affine/blocks/note/src/components/icons.ts @@ -5,23 +5,13 @@ export const NoteNoShadowIcon = html` width="60" height="72" viewBox="0 0 60 72" - fill="none" xmlns="http://www.w3.org/2000/svg" > - - @@ -41,7 +31,7 @@ export const NoteShadowSampleIcon = html` width="32.3077" height="4.61538" rx="2" - fill="black" + fill="currentColor" fill-opacity="0.1" /> diff --git a/blocksuite/affine/blocks/note/src/configs/toolbar.ts b/blocksuite/affine/blocks/note/src/configs/toolbar.ts index 2ed26031c2..751dc91743 100644 --- a/blocksuite/affine/blocks/note/src/configs/toolbar.ts +++ b/blocksuite/affine/blocks/note/src/configs/toolbar.ts @@ -1,19 +1,5 @@ -import { - EdgelessCRUDIdentifier, - EdgelessLegacySlotIdentifier, -} from '@blocksuite/affine-block-surface'; -import { - packColor, - type PickColorEvent, -} from '@blocksuite/affine-components/color-picker'; -import type { LineDetailType } from '@blocksuite/affine-components/edgeless-line-styles-panel'; -import { - DefaultTheme, - NoteBlockModel, - NoteDisplayMode, - type NoteShadow, - resolveColor, -} from '@blocksuite/affine-model'; +import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface'; +import { NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model'; import { NotificationProvider, SidebarExtensionIdentifier, @@ -22,11 +8,9 @@ import { type ToolbarModuleConfig, ToolbarModuleExtension, } from '@blocksuite/affine-shared/services'; -import { getMostCommonResolvedValue } from '@blocksuite/affine-shared/utils'; import { Bound } from '@blocksuite/global/gfx'; import { AutoHeightIcon, - CornerIcon, CustomizedHeightIcon, InsertIntoPageIcon, ScissorsIcon, @@ -35,7 +19,6 @@ import { BlockFlavourIdentifier } from '@blocksuite/std'; import type { ExtensionType } from '@blocksuite/store'; import { computed } from '@preact/signals-core'; import { html } from 'lit'; -import { keyed } from 'lit/directives/keyed.js'; import { changeNoteDisplayMode } from '../commands'; import { NoteConfigExtension } from '../config'; @@ -44,14 +27,6 @@ const trackBaseProps = { category: 'note', }; -const CORNER_LIST = [ - { key: 'None', value: 0 }, - { key: 'Small', value: 8 }, - { key: 'Medium', value: 16 }, - { key: 'Large', value: 24 }, - { key: 'Huge', value: 32 }, -] as const; - const builtinSurfaceToolbarConfig = { actions: [ { @@ -131,64 +106,6 @@ const builtinSurfaceToolbarConfig = { }; }, }, - { - id: 'c.color-picker', - when(ctx) { - const elements = ctx.getSurfaceModelsByType(NoteBlockModel); - return ( - elements.length > 0 && - elements[0].props.displayMode !== NoteDisplayMode.DocOnly - ); - }, - content(ctx) { - const models = ctx.getSurfaceModelsByType(NoteBlockModel); - if (!models.length) return null; - - const enableCustomColor = ctx.features.getFlag('enable_color_picker'); - const theme = ctx.theme.edgeless$.value; - - const firstModel = models[0]; - const background = - getMostCommonResolvedValue( - models.map(model => model.props), - 'background', - background => resolveColor(background, theme) - ) ?? resolveColor(DefaultTheme.noteBackgrounColor, theme); - const onPick = (e: PickColorEvent) => { - const field = 'background'; - - if (e.type === 'pick') { - const color = e.detail.value; - for (const model of models) { - const props = packColor(field, color); - ctx.std - .get(EdgelessCRUDIdentifier) - .updateElement(model.id, props); - } - return; - } - - for (const model of models) { - model[e.type === 'start' ? 'stash' : 'pop'](field); - } - }; - - return html` - - - `; - }, - }, { id: 'd.style', when(ctx) { @@ -200,147 +117,20 @@ const builtinSurfaceToolbarConfig = { }, actions: [ { - id: 'a.shadow-style', - content(ctx) { - const models = ctx.getSurfaceModelsByType(NoteBlockModel); - if (!models.length) return null; - - const theme = ctx.theme.edgeless$.value; - - const firstModel = models[0]; - const { shadowType } = firstModel.props.edgeless.style; - const background = - getMostCommonResolvedValue( - models.map(model => model.props), - 'background', - background => resolveColor(background, theme) - ) ?? resolveColor(DefaultTheme.noteBackgrounColor, theme); - const onSelect = (e: CustomEvent) => { - e.stopPropagation(); - - const shadowType = e.detail; - for (const model of models) { - const edgeless = model.props.edgeless; - ctx.std.get(EdgelessCRUDIdentifier).updateElement(model.id, { - edgeless: { - ...edgeless, - style: { - ...edgeless.style, - shadowType, - }, - }, - }); - } - }; - - return html`${keyed( - firstModel, - html`` - )}`; - }, - } satisfies ToolbarAction, - { - id: 'b.border-style', - content(ctx) { - const models = ctx.getSurfaceModelsByType(NoteBlockModel); - if (!models.length) return null; - - const firstModel = models[0]; - const { borderSize, borderStyle } = firstModel.props.edgeless.style; - const onSelect = (e: CustomEvent) => { - e.stopPropagation(); - - const { type, value } = e.detail; - - if (type === 'size') { - const borderSize = value; - for (const model of models) { - const edgeless = model.props.edgeless; - ctx.std.get(EdgelessCRUDIdentifier).updateElement(model.id, { - edgeless: { - ...edgeless, - style: { - ...edgeless.style, - borderSize, - }, - }, - }); - } - return; - } - - const borderStyle = value; - for (const model of models) { - const edgeless = model.props.edgeless; - ctx.std.get(EdgelessCRUDIdentifier).updateElement(model.id, { - edgeless: { - ...edgeless, - style: { - ...edgeless.style, - borderStyle, - }, - }, - }); - } - }; - - return html`${keyed( - firstModel, - html` - - ` - )}`; - }, - } satisfies ToolbarAction, - { - id: 'c.corners', - label: 'Corners', - content(ctx) { - const models = ctx.getSurfaceModelsByType(NoteBlockModel); - if (!models.length) return null; - - const label = this.label; - const firstModel = models[0]; - const borderRadius$ = computed( - () => firstModel.props.edgeless$.value.style.borderRadius + id: 'b.style', + when: ctx => { + const models = ctx.getSurfaceModels(); + return ( + models.length > 0 && + models.every(model => model instanceof NoteBlockModel) ); - const onSelect = (e: CustomEvent) => { - e.stopPropagation(); - - const borderRadius = e.detail; - for (const model of models) { - const edgeless = model.props.edgeless; - ctx.std.get(EdgelessCRUDIdentifier).updateElement(model.id, { - edgeless: { - ...edgeless, - style: { - ...edgeless.style, - borderRadius, - }, - }, - }); - } - }; - - return html`${keyed( - firstModel, - html`` - )}`; + }, + content(ctx) { + const notes = ctx.getSurfaceModelsByType(NoteBlockModel); + return html``; }, } satisfies ToolbarAction, ], diff --git a/blocksuite/affine/blocks/note/src/effects.ts b/blocksuite/affine/blocks/note/src/effects.ts index 2bff9c8c0c..1c0e4013cf 100644 --- a/blocksuite/affine/blocks/note/src/effects.ts +++ b/blocksuite/affine/blocks/note/src/effects.ts @@ -2,24 +2,21 @@ import { EdgelessNoteBackground } from './components/edgeless-note-background'; import { EdgelessNoteBorderDropdownMenu } from './components/edgeless-note-border-dropdown-menu'; import { EdgelessNoteDisplayModeDropdownMenu } from './components/edgeless-note-display-mode-dropdown-menu'; import { EdgelessNoteMask } from './components/edgeless-note-mask'; -import { EdgelessNoteShadowDropdownMenu } from './components/edgeless-note-shadow-dropdown-menu'; +import { EdgelessNoteShadowMenu } from './components/edgeless-note-shadow-menu'; +import { EdgelessNoteStylePanel } from './components/edgeless-note-style-panel'; import { EdgelessPageBlockTitle } from './components/edgeless-page-block-title'; import { NoteBlockComponent } from './note-block'; import { AFFINE_EDGELESS_NOTE, EdgelessNoteBlockComponent, } from './note-edgeless-block'; - export function effects() { customElements.define('affine-note', NoteBlockComponent); customElements.define(AFFINE_EDGELESS_NOTE, EdgelessNoteBlockComponent); customElements.define('edgeless-note-mask', EdgelessNoteMask); customElements.define('edgeless-note-background', EdgelessNoteBackground); customElements.define('edgeless-page-block-title', EdgelessPageBlockTitle); - customElements.define( - 'edgeless-note-shadow-dropdown-menu', - EdgelessNoteShadowDropdownMenu - ); + customElements.define('edgeless-note-shadow-menu', EdgelessNoteShadowMenu); customElements.define( 'edgeless-note-border-dropdown-menu', EdgelessNoteBorderDropdownMenu @@ -28,4 +25,5 @@ export function effects() { 'edgeless-note-display-mode-dropdown-menu', EdgelessNoteDisplayModeDropdownMenu ); + customElements.define('edgeless-note-style-panel', EdgelessNoteStylePanel); } diff --git a/blocksuite/affine/components/src/color-picker/color-panel.ts b/blocksuite/affine/components/src/color-picker/color-panel.ts index d61f3331e6..262ce983fa 100644 --- a/blocksuite/affine/components/src/color-picker/color-panel.ts +++ b/blocksuite/affine/components/src/color-picker/color-panel.ts @@ -2,7 +2,7 @@ import type { Color, ColorScheme, Palette } from '@blocksuite/affine-model'; import { DefaultTheme, resolveColor } from '@blocksuite/affine-model'; import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { ColorEvent } from '@blocksuite/affine-shared/utils'; -import { css, html, LitElement } from 'lit'; +import { css, html, LitElement, type PropertyValues } from 'lit'; import { property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; @@ -115,12 +115,12 @@ export class EdgelessColorPanel extends LitElement { :host { display: grid; grid-gap: 4px; - grid-template-columns: repeat(9, 1fr); + grid-template-columns: repeat(var(--columns, 9), 1fr); } /* note */ :host(.small) { - grid-template-columns: repeat(6, 1fr); + grid-template-columns: repeat(var(--columns, 6), 1fr); grid-gap: 8px; } @@ -156,6 +156,16 @@ export class EdgelessColorPanel extends LitElement { return this.value && resolveColor(this.value, this.theme); } + override willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has('columns')) { + if (this.columns) { + this.style.setProperty('--columns', this.columns.toString()); + } else { + this.style.removeProperty('--columns'); + } + } + } + override render() { return html` ${repeat( @@ -197,6 +207,9 @@ export class EdgelessColorPanel extends LitElement { @property({ attribute: false }) accessor value: Color | null = null; + + @property({ attribute: false }) + accessor columns: number | undefined = undefined; } export class EdgelessTextColorIcon extends LitElement { diff --git a/blocksuite/affine/components/src/color-picker/icons.ts b/blocksuite/affine/components/src/color-picker/icons.ts index 3d5c987743..3156771d92 100644 --- a/blocksuite/affine/components/src/color-picker/icons.ts +++ b/blocksuite/affine/components/src/color-picker/icons.ts @@ -1,4 +1,5 @@ import { isTransparent } from '@blocksuite/affine-model'; +import { cssVarV2 } from '@blocksuite/affine-shared/theme'; import { html, svg, type TemplateResult } from 'lit'; export function TransparentIcon(hollowCircle = false) { @@ -18,7 +19,7 @@ export function TransparentIcon(hollowCircle = false) { fill-rule="evenodd" clip-rule="evenodd" d="M-1.17405 5.17857C-1.2241 5.5285 -1.25 5.88623 -1.25 6.25V8.39286H1.96429V11.6071H-1.25V13.75C-1.25 14.1138 -1.2241 14.4715 -1.17405 14.8214H1.96429V18.0357H0.0943102C0.602244 18.7639 1.23609 19.3978 1.96429 19.9057V18.0357L5.17857 18.0357V21.174C5.5285 21.2241 5.88623 21.25 6.25 21.25H8.39286V18.0357H11.6071V21.25H13.75C14.1138 21.25 14.4715 21.2241 14.8214 21.174V18.0357H18.0357L18.0357 19.9057C18.7639 19.3978 19.3978 18.7639 19.9057 18.0357L18.0357 18.0357V14.8214H21.174C21.2241 14.4715 21.25 14.1138 21.25 13.75V11.6071H18.0357V8.39286H21.25V6.25C21.25 5.88623 21.2241 5.5285 21.174 5.17857H18.0357V1.96429H19.9057C19.3978 1.23609 18.7639 0.602244 18.0357 0.09431L18.0357 1.96429H14.8214V-1.17405C14.4715 -1.2241 14.1138 -1.25 13.75 -1.25H11.6071V1.96429H8.39286V-1.25H6.25C5.88623 -1.25 5.5285 -1.2241 5.17857 -1.17405V1.96429H1.96429V0.0943099C1.23609 0.602244 0.602244 1.23609 0.0943099 1.96429H1.96429V5.17857H-1.17405ZM5.17857 5.17857V1.96429H8.39286V5.17857H5.17857ZM5.17857 8.39286H1.96429V5.17857H5.17857V8.39286ZM8.39286 8.39286V5.17857H11.6071V8.39286H8.39286ZM8.39286 11.6071V8.39286H5.17857V11.6071H1.96429V14.8214H5.17857V18.0357H8.39286V14.8214H11.6071V18.0357H14.8214V14.8214H18.0357V11.6071H14.8214V8.39286H18.0357V5.17857H14.8214V1.96429H11.6071V5.17857H14.8214V8.39286H11.6071V11.6071H8.39286ZM8.39286 11.6071V14.8214H5.17857V11.6071H8.39286ZM11.6071 11.6071H14.8214V14.8214H11.6071V11.6071Z" - fill="#D9D9D9" + fill="${cssVarV2('icon/tertiary')}" /> ${CircleIcon} diff --git a/blocksuite/affine/components/src/toolbar/icon-button.ts b/blocksuite/affine/components/src/toolbar/icon-button.ts index 720ab7f3d7..35e63b2f8e 100644 --- a/blocksuite/affine/components/src/toolbar/icon-button.ts +++ b/blocksuite/affine/components/src/toolbar/icon-button.ts @@ -1,3 +1,4 @@ +import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import type { Placement } from '@floating-ui/dom'; import type { TemplateResult } from 'lit'; import { css, html, LitElement, nothing } from 'lit'; @@ -19,7 +20,7 @@ export class EditorIconButton extends LitElement { display: flex; align-items: center; padding: var(--icon-container-padding); - color: var(--affine-icon-color); + color: ${unsafeCSSVarV2('icon/primary')}; border-radius: 4px; cursor: pointer; white-space: nowrap; diff --git a/blocksuite/affine/model/src/themes/default.ts b/blocksuite/affine/model/src/themes/default.ts index 4694928f8e..a736fe2fc6 100644 --- a/blocksuite/affine/model/src/themes/default.ts +++ b/blocksuite/affine/model/src/themes/default.ts @@ -38,16 +38,13 @@ const Heavy = { } as const; const NoteBackgroundColorMap = { - Yellow: getColorByKey('edgeless/note/yellow'), - Orange: getColorByKey('edgeless/note/orange'), Red: getColorByKey('edgeless/note/red'), - Magenta: getColorByKey('edgeless/note/magenta'), - Purple: getColorByKey('edgeless/note/purple'), - Blue: getColorByKey('edgeless/note/blue'), - Teal: getColorByKey('edgeless/note/teal'), + Orange: getColorByKey('edgeless/note/orange'), + Yellow: getColorByKey('edgeless/note/yellow'), Green: getColorByKey('edgeless/note/green'), - Black: getColorByKey('edgeless/note/black'), - Grey: getColorByKey('edgeless/note/grey'), + Blue: getColorByKey('edgeless/note/blue'), + Purple: getColorByKey('edgeless/note/purple'), + Magenta: getColorByKey('edgeless/note/magenta'), White: getColorByKey('edgeless/note/white'), Transparent: Transparent, } as const; diff --git a/tests/affine-local/e2e/blocksuite/edgeless/note.spec.ts b/tests/affine-local/e2e/blocksuite/edgeless/note.spec.ts index 764a8de92f..d0038ed310 100644 --- a/tests/affine-local/e2e/blocksuite/edgeless/note.spec.ts +++ b/tests/affine-local/e2e/blocksuite/edgeless/note.spec.ts @@ -315,8 +315,9 @@ test.describe('edgeless note element toolbar', () => { }, }); - await toolbar.getByRole('button', { name: 'Shadow style' }).click(); - await toolbar.getByTestId('affine-note-shadow-film').click(); + await toolbar.getByRole('button', { name: 'Note Style' }).click(); + const noteStylePanel = page.locator('edgeless-note-style-panel'); + await noteStylePanel.getByTestId('affine-note-shadow-film').click(); expect(await getNoteEdgelessProps(page, noteId)).toEqual({ style: { @@ -327,10 +328,11 @@ test.describe('edgeless note element toolbar', () => { }, }); - await toolbar.getByRole('button', { name: 'Border style' }).click(); - await toolbar.locator('.mode-solid').click(); - await toolbar.getByRole('button', { name: 'Border style' }).click(); - await toolbar.locator('affine-slider').getByLabel('8').click(); + const borderStylePanel = noteStylePanel.getByTestId( + 'affine-note-border-style-panel' + ); + await borderStylePanel.locator('.mode-solid').click(); + await borderStylePanel.locator('affine-slider').getByLabel('8').click(); expect(await getNoteEdgelessProps(page, noteId)).toEqual({ style: { @@ -341,12 +343,10 @@ test.describe('edgeless note element toolbar', () => { }, }); - await toolbar.getByRole('button', { name: 'Corners' }).click(); - // TODO(@fundon): delete duplicate components - await toolbar - .locator('affine-size-dropdown-menu') - .getByText('Large') - .click(); + const cornerPanel = noteStylePanel.getByTestId( + 'affine-note-corner-radius-panel' + ); + await cornerPanel.locator('affine-slider').getByLabel('24').click(); expect(await getNoteEdgelessProps(page, noteId)).toEqual({ style: { @@ -357,6 +357,8 @@ test.describe('edgeless note element toolbar', () => { }, }); + await pressEscape(page); + const headerToolbar = page.getByTestId('edgeless-page-block-header'); const toggleButton = headerToolbar.getByTestId( 'edgeless-note-toggle-button' diff --git a/tests/affine-local/e2e/blocksuite/toolbar.spec.ts b/tests/affine-local/e2e/blocksuite/toolbar.spec.ts index 569941ca69..1f02645426 100644 --- a/tests/affine-local/e2e/blocksuite/toolbar.spec.ts +++ b/tests/affine-local/e2e/blocksuite/toolbar.spec.ts @@ -301,12 +301,12 @@ test('should focus on input of popover on toolbar', async ({ page }) => { const scaleValue = await scaleInput.inputValue(); expect(scaleValue).toBe('150'); - const cornersMenu = toolbar.locator('.corners-menu'); - const cornersInput = cornersMenu.locator('input'); - const cornersButton = cornersMenu.getByLabel('Corners'); + const stylePanelButton = toolbar.locator('edgeless-note-style-panel'); + const cornersInput = stylePanelButton.locator( + '.edgeless-note-corner-radius-panel input' + ); - await expect(cornersInput).toBeHidden(); - await cornersButton.click(); + await stylePanelButton.click(); await cornersInput.click(); await expect(cornersInput).toBeFocused(); diff --git a/tests/blocksuite/e2e/clipboard/list.spec.ts b/tests/blocksuite/e2e/clipboard/list.spec.ts index d6d6761268..a11b725085 100644 --- a/tests/blocksuite/e2e/clipboard/list.spec.ts +++ b/tests/blocksuite/e2e/clipboard/list.spec.ts @@ -499,7 +499,7 @@ test(scoped`paste note block with background`, async ({ page }) => { await switchEditorMode(page); await selectNoteInEdgeless(page, ids.noteId); - await triggerComponentToolbarAction(page, 'changeNoteColor'); + await triggerComponentToolbarAction(page, 'changeNoteStyle'); await changeEdgelessNoteBackground(page, 'White'); await assertEdgelessNoteBackground( page, diff --git a/tests/blocksuite/e2e/edgeless/auto-complete.spec.ts b/tests/blocksuite/e2e/edgeless/auto-complete.spec.ts index 60bbc529b1..c3e2b76451 100644 --- a/tests/blocksuite/e2e/edgeless/auto-complete.spec.ts +++ b/tests/blocksuite/e2e/edgeless/auto-complete.spec.ts @@ -191,7 +191,7 @@ test.describe('auto-complete', () => { await page.mouse.click(rect.x + rect.width / 2, rect.y + rect.height / 2); await waitNextFrame(page); - await triggerComponentToolbarAction(page, 'changeNoteColor'); + await triggerComponentToolbarAction(page, 'changeNoteStyle'); await changeEdgelessNoteBackground(page, 'Red'); // move to arrow icon diff --git a/tests/blocksuite/e2e/edgeless/element-toolbar.spec.ts b/tests/blocksuite/e2e/edgeless/element-toolbar.spec.ts index c114852ba4..b6e0e3babe 100644 --- a/tests/blocksuite/e2e/edgeless/element-toolbar.spec.ts +++ b/tests/blocksuite/e2e/edgeless/element-toolbar.spec.ts @@ -57,7 +57,7 @@ test('tooltip should be hidden after clicking on button', async ({ page }) => { await expect(page.locator('note-display-mode-panel')).toBeVisible(); const colorBtn = toolbar.getByRole('button', { - name: 'Background', + name: 'Note Style', }); await colorBtn.hover(); diff --git a/tests/blocksuite/e2e/edgeless/note/note.spec.ts b/tests/blocksuite/e2e/edgeless/note/note.spec.ts index bb1873b121..f11eb3fedb 100644 --- a/tests/blocksuite/e2e/edgeless/note/note.spec.ts +++ b/tests/blocksuite/e2e/edgeless/note/note.spec.ts @@ -317,7 +317,7 @@ test('change note color', async ({ page }) => { ); await selectNoteInEdgeless(page, noteId); - await triggerComponentToolbarAction(page, 'changeNoteColor'); + await triggerComponentToolbarAction(page, 'changeNoteStyle'); await changeEdgelessNoteBackground(page, 'Green'); await assertEdgelessNoteBackground( page, diff --git a/tests/blocksuite/e2e/utils/actions/edgeless.ts b/tests/blocksuite/e2e/utils/actions/edgeless.ts index 4d8c73bb7a..fe15b094b5 100644 --- a/tests/blocksuite/e2e/utils/actions/edgeless.ts +++ b/tests/blocksuite/e2e/utils/actions/edgeless.ts @@ -1057,7 +1057,7 @@ type Action = | 'sendBackward' | 'sendToBack' | 'copyAsPng' - | 'changeNoteColor' + | 'changeNoteStyle' | 'changeShapeStyle' | 'changeShapeColor' | 'changeShapeFillColor' @@ -1253,10 +1253,10 @@ export async function triggerComponentToolbarAction( await button.click(); break; } - case 'changeNoteColor': { - const button = locatorComponentToolbar(page).getByRole('button', { - name: 'Background', - }); + case 'changeNoteStyle': { + const button = locatorComponentToolbar(page).locator( + 'edgeless-note-style-panel' + ); await button.click(); break; } @@ -1373,7 +1373,7 @@ export async function triggerComponentToolbarAction( export async function changeEdgelessNoteBackground(page: Page, label: string) { const colorButton = page - .locator('edgeless-color-picker-button') + .locator('edgeless-note-style-panel') .locator('edgeless-color-panel') .locator(`.color-unit[aria-label="${label}"]`); await colorButton.click();