mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 05:25:53 +08:00
refactor(editor): add a layer of ui-logic to enhance type safety (#12511)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -70,3 +70,19 @@ export const dividerV = css({
|
||||
backgroundColor: 'var(--affine-divider-color)',
|
||||
margin: '0 8px',
|
||||
});
|
||||
|
||||
export const dv = {
|
||||
p2,
|
||||
p4,
|
||||
p8,
|
||||
hover,
|
||||
icon16,
|
||||
icon20,
|
||||
border,
|
||||
round4,
|
||||
round8,
|
||||
color2,
|
||||
shadow2,
|
||||
dividerH,
|
||||
dividerV,
|
||||
};
|
||||
|
||||
@@ -2,42 +2,43 @@ import type {
|
||||
DatabaseAllEvents,
|
||||
EventTraceFn,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { DisposableMember } from '@blocksuite/global/disposable';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { BlockSuiteError } from '@blocksuite/global/exceptions';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import {
|
||||
type Clipboard,
|
||||
type EventName,
|
||||
ShadowlessElement,
|
||||
type UIEventHandler,
|
||||
} from '@blocksuite/std';
|
||||
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
|
||||
import { css, unsafeCSS } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { keyed } from 'lit/directives/keyed.js';
|
||||
import { createRef, ref } from 'lit/directives/ref.js';
|
||||
import { ref } from 'lit/directives/ref.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import { dataViewCommonStyle } from './common/css-variable.js';
|
||||
import type { DataViewSelection, DataViewSelectionState } from './types.js';
|
||||
import type { DataSource } from './data-source/index.js';
|
||||
import type { DataViewSelection } from './types.js';
|
||||
import { cacheComputed } from './utils/cache.js';
|
||||
import { renderUniLit } from './utils/uni-component/index.js';
|
||||
import type { DataViewInstance, DataViewProps } from './view/types.js';
|
||||
import type { DataViewUILogicBase } from './view/data-view-base.js';
|
||||
import type { SingleView } from './view-manager/single-view.js';
|
||||
import type { DataViewWidget } from './widget/index.js';
|
||||
|
||||
type ViewProps = {
|
||||
view: SingleView;
|
||||
selection$: ReadonlySignal<DataViewSelectionState>;
|
||||
setSelection: (selection?: DataViewSelectionState) => void;
|
||||
bindHotkey: DataViewProps['bindHotkey'];
|
||||
handleEvent: DataViewProps['handleEvent'];
|
||||
};
|
||||
|
||||
export type DataViewRendererConfig = Pick<
|
||||
DataViewProps,
|
||||
| 'bindHotkey'
|
||||
| 'handleEvent'
|
||||
| 'virtualPadding$'
|
||||
| 'clipboard'
|
||||
| 'dataSource'
|
||||
| 'headerWidget'
|
||||
| 'onDrag'
|
||||
| 'notification'
|
||||
> & {
|
||||
export type DataViewRendererConfig = {
|
||||
clipboard: Clipboard;
|
||||
onDrag?: (evt: MouseEvent, id: string) => () => void;
|
||||
notification: {
|
||||
toast: (message: string) => void;
|
||||
};
|
||||
virtualPadding$: ReadonlySignal<number>;
|
||||
headerWidget: DataViewWidget | undefined;
|
||||
handleEvent: (name: EventName, handler: UIEventHandler) => DisposableMember;
|
||||
bindHotkey: (hotkeys: Record<string, UIEventHandler>) => DisposableMember;
|
||||
dataSource: DataSource;
|
||||
selection$: ReadonlySignal<DataViewSelection | undefined>;
|
||||
setSelection: (selection: DataViewSelection | undefined) => void;
|
||||
eventTrace: EventTraceFn<DatabaseAllEvents>;
|
||||
@@ -52,7 +53,104 @@ export type DataViewRendererConfig = Pick<
|
||||
};
|
||||
};
|
||||
|
||||
export class DataViewRenderer extends SignalWatcher(
|
||||
export class DataViewRootUILogic {
|
||||
private get dataSource() {
|
||||
return this.config.dataSource;
|
||||
}
|
||||
private get viewManager() {
|
||||
return this.dataSource.viewManager;
|
||||
}
|
||||
private createDataViewUILogic(viewId: string): DataViewUILogicBase {
|
||||
const view = this.viewManager.viewGet(viewId);
|
||||
if (!view) {
|
||||
throw new BlockSuiteError(
|
||||
BlockSuiteError.ErrorCode.DatabaseBlockError,
|
||||
`View ${viewId} not found`
|
||||
);
|
||||
}
|
||||
|
||||
const pcLogic = view.meta.renderer.pcLogic;
|
||||
const mobileLogic = view.meta.renderer.mobileLogic;
|
||||
const logic = (IS_MOBILE ? mobileLogic : pcLogic) ?? pcLogic;
|
||||
|
||||
return new (logic(view))(this, view);
|
||||
}
|
||||
private readonly views$ = cacheComputed(this.viewManager.views$, viewId =>
|
||||
this.createDataViewUILogic(viewId)
|
||||
);
|
||||
private readonly viewsMap$ = computed(() => {
|
||||
return Object.fromEntries(
|
||||
this.views$.list.value.map(logic => [logic.view.id, logic])
|
||||
);
|
||||
});
|
||||
private readonly _uiRef = signal<DataViewRootUI>();
|
||||
|
||||
get selection$() {
|
||||
return this.config.selection$;
|
||||
}
|
||||
|
||||
setSelection(selection?: DataViewSelection) {
|
||||
this.config.setSelection(selection);
|
||||
}
|
||||
|
||||
constructor(public readonly config: DataViewRendererConfig) {}
|
||||
|
||||
get dataViewRenderer() {
|
||||
return this._uiRef.value;
|
||||
}
|
||||
|
||||
readonly currentViewId$ = computed(() => {
|
||||
return this.dataSource.viewManager.currentViewId$.value;
|
||||
});
|
||||
|
||||
readonly currentView$ = computed(() => {
|
||||
const currentViewId = this.currentViewId$.value;
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
return this.viewsMap$.value[currentViewId];
|
||||
});
|
||||
|
||||
focusFirstCell = () => {
|
||||
this.currentView$.value?.focusFirstCell();
|
||||
};
|
||||
|
||||
openDetailPanel = (ops: {
|
||||
view: SingleView;
|
||||
rowId: string;
|
||||
onClose?: () => void;
|
||||
}) => {
|
||||
const openDetailPanel = this.config.detailPanelConfig.openDetailPanel;
|
||||
const target = this.dataViewRenderer;
|
||||
if (openDetailPanel && target) {
|
||||
openDetailPanel(target, {
|
||||
view: ops.view,
|
||||
rowId: ops.rowId,
|
||||
})
|
||||
.catch(console.error)
|
||||
.finally(ops.onClose);
|
||||
}
|
||||
};
|
||||
|
||||
setupViewChangeListener() {
|
||||
let preId: string | undefined = undefined;
|
||||
return this.currentViewId$.subscribe(current => {
|
||||
if (current !== preId) {
|
||||
this.config.setSelection(undefined);
|
||||
}
|
||||
preId = current;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <affine-data-view-renderer
|
||||
${ref(this._uiRef)}
|
||||
.logic="${this}"
|
||||
></affine-data-view-renderer>`;
|
||||
}
|
||||
}
|
||||
|
||||
export class DataViewRootUI extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
@@ -63,63 +161,14 @@ export class DataViewRenderer extends SignalWatcher(
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _view = signal<DataViewInstance>();
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor config!: DataViewRendererConfig;
|
||||
accessor logic!: DataViewRootUILogic;
|
||||
|
||||
private readonly currentViewId$ = computed(() => {
|
||||
return this.config.dataSource.viewManager.currentViewId$.value;
|
||||
});
|
||||
|
||||
viewMap$ = computed(() => {
|
||||
const manager = this.config.dataSource.viewManager;
|
||||
return Object.fromEntries(
|
||||
manager.views$.value.map(view => [view, manager.viewGet(view)])
|
||||
);
|
||||
});
|
||||
|
||||
currentViewConfig$ = computed<ViewProps | undefined>(() => {
|
||||
const currentViewId = this.currentViewId$.value;
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
const view = this.viewMap$.value[currentViewId];
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
view: view,
|
||||
selection$: computed(() => {
|
||||
const selection$ = this.config.selection$;
|
||||
if (selection$.value?.viewId === currentViewId) {
|
||||
return selection$.value;
|
||||
}
|
||||
return;
|
||||
}),
|
||||
setSelection: selection => {
|
||||
this.config.setSelection(selection);
|
||||
},
|
||||
handleEvent: (name, handler) =>
|
||||
this.config.handleEvent(name, context => {
|
||||
return handler(context);
|
||||
}),
|
||||
bindHotkey: hotkeys =>
|
||||
this.config.bindHotkey(
|
||||
Object.fromEntries(
|
||||
Object.entries(hotkeys).map(([key, fn]) => [
|
||||
key,
|
||||
ctx => {
|
||||
return fn(ctx);
|
||||
},
|
||||
])
|
||||
)
|
||||
),
|
||||
};
|
||||
});
|
||||
@state()
|
||||
accessor currentView: string | undefined = undefined;
|
||||
|
||||
focusFirstCell = () => {
|
||||
this.view?.focusFirstCell();
|
||||
this.logic.focusFirstCell();
|
||||
};
|
||||
|
||||
openDetailPanel = (ops: {
|
||||
@@ -127,72 +176,12 @@ export class DataViewRenderer extends SignalWatcher(
|
||||
rowId: string;
|
||||
onClose?: () => void;
|
||||
}) => {
|
||||
const openDetailPanel = this.config.detailPanelConfig.openDetailPanel;
|
||||
if (openDetailPanel) {
|
||||
openDetailPanel(this, {
|
||||
view: ops.view,
|
||||
rowId: ops.rowId,
|
||||
})
|
||||
.catch(console.error)
|
||||
.finally(ops.onClose);
|
||||
}
|
||||
this.logic.openDetailPanel(ops);
|
||||
};
|
||||
|
||||
get view() {
|
||||
return this._view.value;
|
||||
}
|
||||
|
||||
private renderView(viewData?: ViewProps) {
|
||||
if (!viewData) {
|
||||
return;
|
||||
}
|
||||
const props: DataViewProps = {
|
||||
dataViewEle: this,
|
||||
headerWidget: this.config.headerWidget,
|
||||
onDrag: this.config.onDrag,
|
||||
dataSource: this.config.dataSource,
|
||||
virtualPadding$: this.config.virtualPadding$,
|
||||
clipboard: this.config.clipboard,
|
||||
notification: this.config.notification,
|
||||
view: viewData.view,
|
||||
selection$: viewData.selection$,
|
||||
setSelection: viewData.setSelection,
|
||||
bindHotkey: viewData.bindHotkey,
|
||||
handleEvent: viewData.handleEvent,
|
||||
eventTrace: (key, params) => {
|
||||
this.config.eventTrace(key, {
|
||||
...(params as DatabaseAllEvents[typeof key]),
|
||||
viewId: viewData.view.id,
|
||||
viewType: viewData.view.type,
|
||||
});
|
||||
},
|
||||
};
|
||||
const renderer = viewData.view.meta.renderer;
|
||||
const view =
|
||||
(IS_MOBILE ? renderer.mobileView : renderer.view) ?? renderer.view;
|
||||
return keyed(
|
||||
viewData.view.id,
|
||||
renderUniLit(
|
||||
view,
|
||||
{ props },
|
||||
{
|
||||
ref: this._view,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
let preId: string | undefined = undefined;
|
||||
this.disposables.add(
|
||||
this.currentViewId$.subscribe(current => {
|
||||
if (current !== preId) {
|
||||
this.config.setSelection(undefined);
|
||||
}
|
||||
preId = current;
|
||||
})
|
||||
);
|
||||
this.disposables.add(this.logic.setupViewChangeListener());
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -201,34 +190,22 @@ export class DataViewRenderer extends SignalWatcher(
|
||||
'data-view-root': true,
|
||||
'prevent-reference-popup': true,
|
||||
});
|
||||
const currentView = this.logic.currentView$.value;
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
return html`
|
||||
<div style="display: contents" class="${containerClass}">
|
||||
${this.renderView(this.currentViewConfig$.value)}
|
||||
${renderUniLit(currentView.renderer, {
|
||||
logic: currentView,
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
accessor currentView: string | undefined = undefined;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-data-view-renderer': DataViewRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
export class DataView {
|
||||
private readonly _ref = createRef<DataViewRenderer>();
|
||||
|
||||
get expose() {
|
||||
return this._ref.value?.view;
|
||||
}
|
||||
|
||||
render(props: DataViewRendererConfig) {
|
||||
return html` <affine-data-view-renderer
|
||||
${ref(this._ref)}
|
||||
.config="${props}"
|
||||
></affine-data-view-renderer>`;
|
||||
'affine-data-view-renderer': DataViewRootUI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DataViewPropertiesSettingView } from './common/properties.js';
|
||||
import { Button } from './component/button/button.js';
|
||||
import { Overflow } from './component/overflow/overflow.js';
|
||||
import { MultiTagSelect, MultiTagView } from './component/tags/index.js';
|
||||
import { DataViewRenderer } from './data-view.js';
|
||||
import { DataViewRootUI } from './data-view.js';
|
||||
import { RecordDetail } from './detail/detail.js';
|
||||
import { RecordField } from './detail/field.js';
|
||||
import { VariableRefView } from './expression/ref/ref-view.js';
|
||||
@@ -15,7 +15,7 @@ import { AffineLitIcon, UniAnyRender, UniLit } from './index.js';
|
||||
import { AnyRender } from './utils/uni-component/render-template.js';
|
||||
|
||||
export function coreEffects() {
|
||||
customElements.define('affine-data-view-renderer', DataViewRenderer);
|
||||
customElements.define('affine-data-view-renderer', DataViewRootUI);
|
||||
customElements.define('any-render', AnyRender);
|
||||
customElements.define(
|
||||
'data-view-properties-setting',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './common/index.js';
|
||||
export * from './component/index.js';
|
||||
export { DataSourceBase } from './data-source/base.js';
|
||||
export { DataView } from './data-view.js';
|
||||
export { DataViewRootUILogic } from './data-view.js';
|
||||
export * from './filter/index.js';
|
||||
export * from './group-by';
|
||||
export * from './logical/index.js';
|
||||
|
||||
@@ -183,7 +183,6 @@ export class TypeSystem {
|
||||
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||
if (realArg != null) {
|
||||
if (!this._unify(newCtx, realArg, arg)) {
|
||||
console.log('arg', realArg, arg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
||||
|
||||
export const cacheComputed = <T>(
|
||||
ids: ReadonlySignal<string[]>,
|
||||
create: (id: string) => T
|
||||
) => {
|
||||
const cache = new Map<string, T>();
|
||||
const getOrCreate = (id: string): T => {
|
||||
if (cache.has(id)) {
|
||||
return cache.get(id)!;
|
||||
}
|
||||
const value = create(id);
|
||||
if (value) {
|
||||
cache.set(id, value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
return {
|
||||
getOrCreate,
|
||||
list: computed<T[]>(() => {
|
||||
const list = ids.value;
|
||||
const keys = new Set(cache.keys());
|
||||
for (const [cachedId] of cache) {
|
||||
keys.delete(cachedId);
|
||||
}
|
||||
for (const id of keys) {
|
||||
cache.delete(id);
|
||||
}
|
||||
return list.map(id => getOrCreate(id));
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './lazy.js';
|
||||
export * from './uni-component/index.js';
|
||||
export * from './uni-icon.js';
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const lazy = <T>(fn: () => T): { value: T } => {
|
||||
let data: { value: T } | undefined;
|
||||
return {
|
||||
get value() {
|
||||
if (!data) {
|
||||
data = { value: fn() };
|
||||
}
|
||||
return data.value;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,17 +1,106 @@
|
||||
import type {
|
||||
DatabaseAllEvents,
|
||||
DatabaseAllViewEvents,
|
||||
EventTraceFn,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
|
||||
import type { DisposableMember } from '@blocksuite/global/disposable';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import {
|
||||
type EventName,
|
||||
ShadowlessElement,
|
||||
type UIEventHandler,
|
||||
} from '@blocksuite/std';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { DataViewRootUILogic } from '../data-view.js';
|
||||
import type { DataViewSelection } from '../types.js';
|
||||
import type { SingleView } from '../view-manager/single-view.js';
|
||||
import type { DataViewWidget } from '../widget/index.js';
|
||||
import type { DataViewInstance, DataViewProps } from './types.js';
|
||||
|
||||
export abstract class DataViewBase<
|
||||
T extends SingleView = SingleView,
|
||||
Selection extends DataViewSelection = DataViewSelection,
|
||||
> extends SignalWatcher(WithDisposable(ShadowlessElement)) {
|
||||
abstract expose: DataViewInstance;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor props!: DataViewProps<T, Selection>;
|
||||
accessor props!: DataViewProps<Selection>;
|
||||
}
|
||||
export abstract class DataViewUIBase<
|
||||
Logic extends DataViewUILogicBase = DataViewUILogicBase,
|
||||
> extends SignalWatcher(WithDisposable(ShadowlessElement)) {
|
||||
@property({ attribute: false })
|
||||
accessor logic!: Logic;
|
||||
}
|
||||
|
||||
export abstract class DataViewUILogicBase<
|
||||
T extends SingleView = SingleView,
|
||||
Selection extends DataViewSelection = DataViewSelection,
|
||||
> {
|
||||
constructor(
|
||||
public readonly root: DataViewRootUILogic,
|
||||
public readonly view: T
|
||||
) {}
|
||||
|
||||
get headerWidget(): DataViewWidget | undefined {
|
||||
return this.root.config.headerWidget;
|
||||
}
|
||||
bindHotkey(hotkeys: Record<string, UIEventHandler>): DisposableMember {
|
||||
return this.root.config.bindHotkey(
|
||||
Object.fromEntries(
|
||||
Object.entries(hotkeys).map(([key, fn]) => [
|
||||
key,
|
||||
ctx => {
|
||||
return fn(ctx);
|
||||
},
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
handleEvent(name: EventName, handler: UIEventHandler): DisposableMember {
|
||||
return this.root.config.handleEvent(name, context => {
|
||||
return handler(context);
|
||||
});
|
||||
}
|
||||
setSelection(selection?: Selection): void {
|
||||
this.root.setSelection(selection);
|
||||
}
|
||||
|
||||
selection$ = computed<Selection | undefined>(() => {
|
||||
const selection$ = this.root.selection$;
|
||||
if (selection$.value?.viewId === this.view.id) {
|
||||
return selection$.value as Selection | undefined;
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
eventTrace: EventTraceFn<DatabaseAllViewEvents> = (key, params) => {
|
||||
this.root.config.eventTrace(key, {
|
||||
...(params as DatabaseAllEvents[typeof key]),
|
||||
viewId: this.view.id,
|
||||
viewType: this.view.type,
|
||||
});
|
||||
};
|
||||
|
||||
abstract clearSelection: () => void;
|
||||
abstract addRow: (position: InsertToPosition) => string | undefined;
|
||||
abstract focusFirstCell: () => void;
|
||||
abstract showIndicator: (evt: MouseEvent) => boolean;
|
||||
abstract hideIndicator: () => void;
|
||||
abstract moveTo: (id: string, evt: MouseEvent) => void;
|
||||
|
||||
abstract renderer: UniComponent<{
|
||||
logic: DataViewUILogicBase<T, Selection>;
|
||||
}>;
|
||||
}
|
||||
|
||||
type Constructor<T extends abstract new (...args: any) => any> = new (
|
||||
...args: ConstructorParameters<T>
|
||||
) => InstanceType<T>;
|
||||
|
||||
export type DataViewUILogicBaseConstructor = Constructor<
|
||||
typeof DataViewUILogicBase
|
||||
>;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
|
||||
import type { SingleView } from '../view-manager/single-view.js';
|
||||
import type { ViewManager } from '../view-manager/view-manager.js';
|
||||
import type { DataViewUILogicBaseConstructor } from './data-view-base.js';
|
||||
import type { DataViewInstance, DataViewProps } from './types.js';
|
||||
|
||||
export type BasicViewDataType<
|
||||
@@ -48,9 +49,10 @@ type DataViewComponent = UniComponent<
|
||||
>;
|
||||
|
||||
export interface DataViewRendererConfig {
|
||||
view: DataViewComponent;
|
||||
mobileView?: DataViewComponent;
|
||||
icon: UniComponent;
|
||||
pcLogic: (view: SingleView) => DataViewUILogicBaseConstructor;
|
||||
mobileLogic?: (view: SingleView) => DataViewUILogicBaseConstructor;
|
||||
}
|
||||
|
||||
export type ViewMeta<
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './convert.js';
|
||||
export * from './data-view.js';
|
||||
export * from './data-view-base.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -4,44 +4,21 @@ import type {
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
|
||||
import type { Disposable } from '@blocksuite/global/disposable';
|
||||
import type { Clipboard, EventName, UIEventHandler } from '@blocksuite/std';
|
||||
import type { EventName, UIEventHandler } from '@blocksuite/std';
|
||||
import type { ReadonlySignal } from '@preact/signals-core';
|
||||
|
||||
import type { DataSource } from '../common/index.js';
|
||||
import type { DataViewRenderer } from '../data-view.js';
|
||||
import type { DataViewSelection } from '../types.js';
|
||||
import type { SingleView } from '../view-manager/index.js';
|
||||
import type { DataViewWidget } from '../widget/index.js';
|
||||
|
||||
export interface DataViewProps<
|
||||
T extends SingleView = SingleView,
|
||||
Selection extends DataViewSelection = DataViewSelection,
|
||||
> {
|
||||
dataViewEle: DataViewRenderer;
|
||||
|
||||
headerWidget?: DataViewWidget;
|
||||
|
||||
view: T;
|
||||
dataSource: DataSource;
|
||||
|
||||
bindHotkey: (hotkeys: Record<string, UIEventHandler>) => Disposable;
|
||||
|
||||
handleEvent: (name: EventName, handler: UIEventHandler) => Disposable;
|
||||
|
||||
setSelection: (selection?: Selection) => void;
|
||||
|
||||
selection$: ReadonlySignal<Selection | undefined>;
|
||||
|
||||
virtualPadding$: ReadonlySignal<number>;
|
||||
|
||||
onDrag?: (evt: MouseEvent, id: string) => () => void;
|
||||
|
||||
clipboard: Clipboard;
|
||||
|
||||
notification: {
|
||||
toast: (message: string) => void;
|
||||
};
|
||||
|
||||
eventTrace: EventTraceFn<DatabaseAllViewEvents>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
|
||||
import type { DataViewInstance } from '../view/types.js';
|
||||
import type { DataViewUILogicBase } from '../view/data-view-base.js';
|
||||
|
||||
export type DataViewWidgetProps = {
|
||||
dataViewInstance: DataViewInstance;
|
||||
export type DataViewWidgetProps<
|
||||
ViewLogic extends DataViewUILogicBase = DataViewUILogicBase,
|
||||
> = {
|
||||
dataViewLogic: ViewLogic;
|
||||
};
|
||||
export type DataViewWidget = UniComponent<DataViewWidgetProps>;
|
||||
|
||||
@@ -2,30 +2,27 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { DataViewInstance } from '../view/types.js';
|
||||
import type { SingleView } from '../view-manager/index.js';
|
||||
import type { DataViewUILogicBase } from '../view/data-view-base.js';
|
||||
import type { DataViewWidgetProps } from './types.js';
|
||||
|
||||
export class WidgetBase<View extends SingleView = SingleView>
|
||||
export class WidgetBase<
|
||||
ViewLogic extends DataViewUILogicBase = DataViewUILogicBase,
|
||||
>
|
||||
extends SignalWatcher(WithDisposable(ShadowlessElement))
|
||||
implements DataViewWidgetProps
|
||||
implements DataViewWidgetProps<ViewLogic>
|
||||
{
|
||||
get dataSource() {
|
||||
return this.view.manager.dataSource;
|
||||
return this.viewManager.dataSource;
|
||||
}
|
||||
|
||||
get view() {
|
||||
return this.dataViewInstance.view;
|
||||
return this.dataViewLogic.view;
|
||||
}
|
||||
|
||||
get viewManager() {
|
||||
return this.view.manager;
|
||||
}
|
||||
|
||||
get viewMethods() {
|
||||
return this.dataViewInstance;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor dataViewInstance!: DataViewInstance<View>;
|
||||
accessor dataViewLogic!: ViewLogic;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user