From 0f1a3c212d2f0f5952f5caa37cc9e22e1d54d618 Mon Sep 17 00:00:00 2001 From: zzj3720 <17165520+zzj3720@users.noreply.github.com> Date: Tue, 27 May 2025 09:36:44 +0000 Subject: [PATCH] refactor(editor): add a layer of ui-logic to enhance type safety (#12511) ## Summary by CodeRabbit - **New Features** - Introduced modular UI logic layers for Kanban and Table views, enhancing maintainability and scalability. - Added new CSS-in-JS style modules for database blocks and table views, improving visual consistency. - Expanded telemetry event tracking for database views, properties, filters, and groups. - Added utility functions for lazy initialization and cached computed values. - **Refactor** - Unified logic and state management across Kanban and Table views by replacing direct component dependencies with logic-centric architecture. - Updated components and widgets to use the new logic-based approach for state, selection, and event handling. - Replaced inline styles with CSS classes; updated class names to align with new component structure. - Centralized state access through UI logic instances, eliminating direct DOM queries and simplifying dependencies. - Consolidated Kanban and Table view presets effects for streamlined initialization. - Replaced Lit reactive state with Preact signals in multiple components for improved reactivity. - Split monolithic components into separate logic and UI classes for clearer separation of concerns. - Removed obsolete components and consolidated exports for cleaner API surface. - **Bug Fixes** - Enhanced selection and interaction reliability in database cells and views. - Fixed scrolling issues on mobile table views for improved compatibility. - **Chores** - Updated end-to-end test selectors to reflect new component names and structure. - Removed deprecated utilities and cleaned up unused imports. - **Documentation** - Improved type definitions and public API exports for better developer experience. --- .../blocks/data-view/src/data-view-block.ts | 126 ++++--- .../database/src/components/title/index.ts | 60 ++-- .../database/src/database-block-styles.ts | 73 ++++ .../blocks/database/src/database-block.ts | 332 ++++++++---------- .../data-view/src/core/common/dv-css.ts | 16 + .../affine/data-view/src/core/data-view.ts | 297 ++++++++-------- .../affine/data-view/src/core/effect.ts | 4 +- blocksuite/affine/data-view/src/core/index.ts | 2 +- .../data-view/src/core/logical/type-system.ts | 1 - .../affine/data-view/src/core/utils/cache.ts | 32 ++ .../affine/data-view/src/core/utils/index.ts | 1 + .../affine/data-view/src/core/utils/lazy.ts | 11 + .../data-view/src/core/view/data-view-base.ts | 95 ++++- .../data-view/src/core/view/data-view.ts | 4 +- .../affine/data-view/src/core/view/index.ts | 1 + .../affine/data-view/src/core/view/types.ts | 25 +- .../affine/data-view/src/core/widget/types.ts | 8 +- .../data-view/src/core/widget/widget-base.ts | 19 +- .../data-view/src/view-presets/effect.ts | 49 +-- .../src/view-presets/kanban/effect.ts | 7 + .../src/view-presets/kanban/index.ts | 1 - .../src/view-presets/kanban/mobile/card.ts | 22 +- .../src/view-presets/kanban/mobile/cell.ts | 22 +- .../src/view-presets/kanban/mobile/effect.ts | 11 + .../src/view-presets/kanban/mobile/group.ts | 15 +- .../kanban/mobile/kanban-view-ui-logic.ts | 168 +++++++++ .../view-presets/kanban/mobile/kanban-view.ts | 149 -------- .../src/view-presets/kanban/mobile/menu.ts | 24 +- .../src/view-presets/kanban/pc/card.ts | 35 +- .../src/view-presets/kanban/pc/cell.ts | 26 +- .../kanban/pc/controller/clipboard.ts | 42 ++- .../view-presets/kanban/pc/controller/drag.ts | 93 ++--- .../kanban/pc/controller/hotkeys.ts | 96 ++--- .../kanban/pc/controller/selection.ts | 75 ++-- .../src/view-presets/kanban/pc/effect.ts | 11 + .../src/view-presets/kanban/pc/group.ts | 65 ++-- .../kanban/pc/kanban-view-ui-logic.ts | 330 +++++++++++++++++ .../src/view-presets/kanban/pc/kanban-view.ts | 300 ---------------- .../src/view-presets/kanban/pc/menu.ts | 10 +- .../src/view-presets/kanban/renderer.ts | 11 +- .../src/view-presets/table/effect.ts | 11 + .../data-view/src/view-presets/table/index.ts | 2 - .../src/view-presets/table/mobile/cell.ts | 18 +- .../src/view-presets/table/mobile/effect.ts | 15 + .../src/view-presets/table/mobile/group.ts | 55 +-- .../src/view-presets/table/mobile/header.ts | 3 +- .../src/view-presets/table/mobile/menu.ts | 6 +- .../src/view-presets/table/mobile/row.ts | 19 +- .../table/mobile/table-view-style.ts | 27 ++ .../table/mobile/table-view-ui-logic.ts | 160 +++++++++ .../view-presets/table/mobile/table-view.ts | 215 ------------ .../table/pc-virtual/controller/clipboard.ts | 63 ++-- .../pc-virtual/controller/drag-to-fill.ts | 8 +- .../table/pc-virtual/controller/drag.ts | 15 +- .../table/pc-virtual/controller/hotkeys.ts | 24 +- .../table/pc-virtual/controller/selection.ts | 52 ++- .../view-presets/table/pc-virtual/effect.ts | 6 +- .../pc-virtual/group/bottom/group-footer.ts | 10 +- .../pc-virtual/group/top/group-header.ts | 12 +- .../group/top/header/column-header.ts | 23 +- .../group/top/header/column-move-preview.ts | 16 +- .../group/top/header/single-column-header.ts | 55 ++- .../view-presets/table/pc-virtual/row/cell.ts | 18 +- .../view-presets/table/pc-virtual/row/menu.ts | 14 +- .../table/pc-virtual/row/row-header.ts | 14 +- .../table/pc-virtual/row/row-last.ts | 8 +- .../{table-view.ts => table-view-ui-logic.ts} | 244 ++++++------- .../pc-virtual/virtual/batch-task-manager.ts | 26 +- .../src/view-presets/table/pc/cell.ts | 36 +- .../table/pc/controller/clipboard.ts | 61 ++-- .../table/pc/controller/drag-to-fill.ts | 8 +- .../view-presets/table/pc/controller/drag.ts | 33 +- .../table/pc/controller/hotkeys.ts | 22 +- .../table/pc/controller/selection.ts | 61 ++-- .../src/view-presets/table/pc/effect.ts | 11 +- .../src/view-presets/table/pc/group.ts | 31 +- .../table/pc/header/column-header.ts | 17 +- .../table/pc/header/column-renderer.ts | 16 +- .../table/pc/header/database-header-column.ts | 18 +- .../src/view-presets/table/pc/menu.ts | 14 +- .../table/pc/row/row-select-checkbox.ts | 14 +- .../src/view-presets/table/pc/row/row.ts | 35 +- .../view-presets/table/pc/table-view-style.ts | 112 ++++++ .../table/pc/table-view-ui-logic.ts | 220 ++++++++++++ .../src/view-presets/table/pc/table-view.ts | 306 ---------------- .../src/view-presets/table/renderer.ts | 17 +- .../table/stats/column-stats-bar.ts | 8 +- .../src/view-presets/table/stats/effect.ts | 10 + .../src/view-presets/table/styles.ts | 8 + .../view-presets/table/table-view-selector.ts | 45 --- .../quick-setting-bar/filter/index.ts | 2 +- .../widget-presets/quick-setting-bar/index.ts | 2 +- .../quick-setting-bar/sort/index.ts | 4 +- .../tools/presets/search/search.ts | 14 +- .../widget-presets/tools/presets/sort/sort.ts | 2 +- .../tools/presets/table-add-row/add-row.ts | 14 +- .../presets/view-options/view-options.ts | 20 +- .../src/widget-presets/tools/tools-view.ts | 2 +- .../widget-presets/views-bar/views-view.ts | 4 +- .../services/telemetry-service/database.ts | 28 +- .../shared/src/utils/dom/point-to-block.ts | 2 +- .../e2e/blocksuite/database/utils.ts | 4 +- .../affine-local/e2e/page-properties.spec.ts | 2 +- tests/blocksuite/e2e/database/actions.ts | 13 +- .../blocksuite/e2e/database/database.spec.ts | 2 +- .../e2e/database/statistics.spec.ts | 6 +- tests/blocksuite/e2e/drag.spec.ts | 11 +- tests/blocksuite/e2e/utils/actions/misc.ts | 12 - tests/kit/src/utils/page-logic.ts | 6 +- 109 files changed, 2625 insertions(+), 2436 deletions(-) create mode 100644 blocksuite/affine/blocks/database/src/database-block-styles.ts create mode 100644 blocksuite/affine/data-view/src/core/utils/cache.ts create mode 100644 blocksuite/affine/data-view/src/core/utils/lazy.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/effect.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/mobile/effect.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/mobile/kanban-view-ui-logic.ts delete mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/mobile/kanban-view.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/pc/effect.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/pc/kanban-view-ui-logic.ts delete mode 100644 blocksuite/affine/data-view/src/view-presets/kanban/pc/kanban-view.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/effect.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/mobile/effect.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/mobile/table-view-style.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/mobile/table-view-ui-logic.ts delete mode 100644 blocksuite/affine/data-view/src/view-presets/table/mobile/table-view.ts rename blocksuite/affine/data-view/src/view-presets/table/pc-virtual/{table-view.ts => table-view-ui-logic.ts} (67%) create mode 100644 blocksuite/affine/data-view/src/view-presets/table/pc/table-view-style.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/pc/table-view-ui-logic.ts delete mode 100644 blocksuite/affine/data-view/src/view-presets/table/pc/table-view.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/stats/effect.ts create mode 100644 blocksuite/affine/data-view/src/view-presets/table/styles.ts delete mode 100644 blocksuite/affine/data-view/src/view-presets/table/table-view-selector.ts diff --git a/blocksuite/affine/blocks/data-view/src/data-view-block.ts b/blocksuite/affine/blocks/data-view/src/data-view-block.ts index 2aae05240..35bcf3d5b 100644 --- a/blocksuite/affine/blocks/data-view/src/data-view-block.ts +++ b/blocksuite/affine/blocks/data-view/src/data-view-block.ts @@ -23,9 +23,9 @@ import { createRecordDetail, createUniComponentFromWebComponent, type DataSource, - DataView, dataViewCommonStyle, type DataViewProps, + DataViewRootUILogic, type DataViewSelection, type DataViewWidget, type DataViewWidgetProps, @@ -133,8 +133,6 @@ export class DataViewBlockComponent extends CaptionedBlockComponent { return { dispose: this.host.event.bindHotkey(hotkeys, { @@ -232,10 +230,6 @@ export class DataViewBlockComponent extends CaptionedBlockComponent { + const notification = this.std.getOptional(NotificationProvider); + if (notification) { + notification.toast(message); + } else { + toast(this.host, message); + } + }, + }, + eventTrace: (key, params) => { + const telemetryService = this.std.getOptional(TelemetryProvider); + telemetryService?.track(key, { + ...(params as TelemetryEventMap[typeof key]), + blockId: this.blockId, + }); + }, + detailPanelConfig: { + openDetailPanel: (target, data) => { + const peekViewService = this.std.getOptional(PeekViewProvider); + if (peekViewService) { + const template = createRecordDetail({ + ...data, + openDoc: () => {}, + detail: { + header: uniMap( + createUniComponentFromWebComponent(BlockRenderer), + props => ({ + ...props, + host: this.host, + }) + ), + note: uniMap( + createUniComponentFromWebComponent(NoteRenderer), + props => ({ + ...props, + model: this.model, + host: this.host, + }) + ), + }, + }); + return peekViewService.peek({ target, template }); + } else { + return Promise.resolve(); + } + }, + }, + }); override renderBlock() { - const peekViewService = this.std.getOptional(PeekViewProvider); - const telemetryService = this.std.getOptional(TelemetryProvider); return html`
- ${this.dataView.render({ - virtualPadding$: signal(0), - bindHotkey: this._bindHotkey, - handleEvent: this._handleEvent, - selection$: this.selection$, - setSelection: this.setSelection, - dataSource: this.dataSource, - headerWidget: this.headerWidget, - clipboard: this.std.clipboard, - notification: { - toast: message => { - const notification = this.std.getOptional(NotificationProvider); - if (notification) { - notification.toast(message); - } else { - toast(this.host, message); - } - }, - }, - eventTrace: (key, params) => { - telemetryService?.track(key, { - ...(params as TelemetryEventMap[typeof key]), - blockId: this.blockId, - }); - }, - detailPanelConfig: { - openDetailPanel: (target, data) => { - if (peekViewService) { - const template = createRecordDetail({ - ...data, - openDoc: () => {}, - detail: { - header: uniMap( - createUniComponentFromWebComponent(BlockRenderer), - props => ({ - ...props, - host: this.host, - }) - ), - note: uniMap( - createUniComponentFromWebComponent(NoteRenderer), - props => ({ - ...props, - model: this.model, - host: this.host, - }) - ), - }, - }); - return peekViewService.peek({ target, template }); - } else { - return Promise.resolve(); - } - }, - }, - })} + ${this.dataViewRootLogic.render()}
`; } diff --git a/blocksuite/affine/blocks/database/src/components/title/index.ts b/blocksuite/affine/blocks/database/src/components/title/index.ts index 4c4d1f556..4a49c7d93 100644 --- a/blocksuite/affine/blocks/database/src/components/title/index.ts +++ b/blocksuite/affine/blocks/database/src/components/title/index.ts @@ -1,15 +1,19 @@ import { stopPropagation } from '@blocksuite/affine-shared/utils'; -import { WithDisposable } from '@blocksuite/global/lit'; +import type { DataViewUILogicBase } from '@blocksuite/data-view'; +import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; import { ShadowlessElement } from '@blocksuite/std'; import type { Text } from '@blocksuite/store'; +import { signal } from '@preact/signals-core'; import { css, html } from 'lit'; -import { property, query, state } from 'lit/decorators.js'; +import { property, query } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { styleMap } from 'lit/directives/style-map.js'; import type { DatabaseBlockComponent } from '../../database-block.js'; -export class DatabaseTitle extends WithDisposable(ShadowlessElement) { +export class DatabaseTitle extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { static override styles = css` .affine-database-title { position: relative; @@ -71,22 +75,23 @@ export class DatabaseTitle extends WithDisposable(ShadowlessElement) { `; private readonly compositionEnd = () => { + this.isComposing$.value = false; this.titleText.replace(0, this.titleText.length, this.input.value); }; private readonly onBlur = () => { - this.isFocus = false; + this.isFocus$.value = false; }; private readonly onFocus = () => { - this.isFocus = true; - if (this.database?.viewSelection$?.value) { - this.database?.setSelection(undefined); + this.isFocus$.value = true; + if (this.dataViewLogic.selection$.value) { + this.dataViewLogic.setSelection(undefined); } }; private readonly onInput = (e: InputEvent) => { - this.text = this.input.value; + this.text$.value = this.input.value; if (!e.isComposing) { this.titleText.replace(0, this.titleText.length, this.input.value); } @@ -102,9 +107,9 @@ export class DatabaseTitle extends WithDisposable(ShadowlessElement) { }; updateText = () => { - if (!this.isFocus) { + if (!this.isFocus$.value) { this.input.value = this.titleText.toString(); - this.text = this.input.value; + this.text$.value = this.input.value; } }; @@ -124,25 +129,25 @@ export class DatabaseTitle extends WithDisposable(ShadowlessElement) { } override render() { - const isEmpty = !this.text; + const isEmpty = !this.text$.value; const classList = classMap({ 'affine-database-title': true, - ellipsis: !this.isFocus, + ellipsis: !this.isFocus$.value, }); const untitledStyle = styleMap({ height: isEmpty ? 'auto' : 0, - opacity: isEmpty && !this.isFocus ? 1 : 0, + opacity: isEmpty && !this.isFocus$.value ? 1 : 0, }); return html`
Untitled
-
${this.text}
+
${this.text$.value}