import type { InsertToPosition } from '@blocksuite/affine-shared/utils'; import type { GeneralServiceIdentifier } from '@blocksuite/global/di'; import { computed, type ReadonlySignal, signal } from '@preact/signals-core'; import type { Variable } from '../expression/types.js'; import type { PropertyMetaConfig } from '../property/property-config.js'; import type { TraitKey } from '../traits/key.js'; import type { DatabaseFlags } from '../types.js'; import { computedLock } from '../utils/lock.js'; import type { DataViewDataType, ViewMeta } from '../view/data-view.js'; import { type Cell, CellBase } from './cell.js'; import type { Property } from './property.js'; import { type Row, RowBase } from './row.js'; import type { ViewManager } from './view-manager.js'; export type MainProperties = { titleColumn?: string; iconColumn?: string; imageColumn?: string; }; export interface SingleView { readonly id: string; readonly type: string; readonly manager: ViewManager; readonly meta: ViewMeta; readonly readonly$: ReadonlySignal; delete(): void; duplicate(): void; readonly name$: ReadonlySignal; nameSet(name: string): void; readonly propertiesRaw$: ReadonlySignal; readonly propertyMap$: ReadonlySignal>; readonly properties$: ReadonlySignal; readonly propertyIds$: ReadonlySignal; readonly detailProperties$: ReadonlySignal; readonly rowsRaw$: ReadonlySignal; readonly rows$: ReadonlySignal; readonly rowIds$: ReadonlySignal; readonly vars$: ReadonlySignal; readonly featureFlags$: ReadonlySignal; propertyGetOrCreate(propertyId: string): Property; rowGetOrCreate(rowId: string): Row; cellGetOrCreate(rowId: string, propertyId: string): Cell; rowAdd(insertPosition: InsertToPosition): string; rowsDelete(rows: string[]): void; readonly propertyMetas$: ReadonlySignal; propertyAdd( toAfterOfProperty: InsertToPosition, ops?: { type?: string; name?: string; } ): string | undefined; serviceGet(key: GeneralServiceIdentifier): T | null; serviceGetOrCreate(key: GeneralServiceIdentifier, create: () => T): T; traitGet(key: TraitKey): T | undefined; mainProperties$: ReadonlySignal; lockRows(lock: boolean): void; isLocked$: ReadonlySignal; } export abstract class SingleViewBase< ViewData extends DataViewDataType = DataViewDataType, > implements SingleView { private readonly searchString = signal(''); private readonly traitMap = new Map(); data$ = computed(() => { return this.dataSource.viewDataGet(this.id) as ViewData | undefined; }); abstract detailProperties$: ReadonlySignal; protected lockRows$ = signal(false); isLocked$ = computed(() => { return this.lockRows$.value; }); abstract mainProperties$: ReadonlySignal; name$: ReadonlySignal = computed(() => { return this.data$.value?.name ?? ''; }); propertyIds$: ReadonlySignal = computed(() => { return this.properties$.value.map(v => v.id); }); propertyMap$: ReadonlySignal> = computed(() => { return Object.fromEntries(this.properties$.value.map(v => [v.id, v])); }); abstract properties$: ReadonlySignal; abstract propertiesRaw$: ReadonlySignal; abstract readonly$: ReadonlySignal; rowsRaw$ = computed(() => { return this.dataSource.rows$.value.map(id => this.rowGetOrCreate(id)); }); rows$ = computedLock( computed(() => { return this.rowsMapping(this.rowsRaw$.value); }), this.isLocked$ ); rowsDelete(rows: string[]): void { this.lockRows(false); this.dataSource.rowDelete(rows); } rowIds$ = computed(() => { return this.rowsRaw$.value.map(v => v.rowId); }); vars$ = computed(() => { return this.propertiesRaw$.value.flatMap(property => { const propertyMeta = this.dataSource.propertyMetaGet( property.type$.value ); if (!propertyMeta) { return []; } return { id: property.id, name: property.name$.value, type: propertyMeta.config.jsonValue.type({ data: property.data$.value, dataSource: this.dataSource, }), icon: property.icon, propertyType: property.type$.value, }; }); }); protected get dataSource() { return this.manager.dataSource; } get featureFlags$() { return this.dataSource.featureFlags$; } get isLocked() { return this.lockRows$.value; } get meta() { return this.dataSource.viewMetaGet(this.type); } get propertyMetas$() { return this.dataSource.propertyMetas$; } abstract get type(): string; constructor( public manager: ViewManager, public id: string ) {} private searchRowsMapping(rows: Row[], searchString: string): Row[] { return rows.filter(row => { if (searchString) { const containsSearchString = this.propertyIds$.value.some( propertyId => { return this.cellGetOrCreate(row.rowId, propertyId) .stringValue$.value?.toLowerCase() .includes(searchString?.toLowerCase()); } ); if (!containsSearchString) { return false; } } return this.isShow(row.rowId); }); } cellGetOrCreate(rowId: string, propertyId: string): Cell { return new CellBase(this, propertyId, rowId); } serviceGet(key: GeneralServiceIdentifier): T | null { return this.dataSource.serviceGet(key); } serviceGetOrCreate(key: GeneralServiceIdentifier, create: () => T): T { return this.dataSource.serviceGetOrCreate(key, create); } dataUpdate(updater: (viewData: ViewData) => Partial): void { this.dataSource.viewDataUpdate(this.id, updater); } delete(): void { this.manager.viewDelete(this.id); } duplicate(): void { this.manager.viewDuplicate(this.id); } abstract isShow(rowId: string): boolean; lockRows(lock: boolean) { this.lockRows$.value = lock; } nameSet(name: string): void { this.dataUpdate(() => { return { name, } as ViewData; }); } propertyAdd( position: InsertToPosition, ops?: { type?: string; name?: string; } ): string | undefined { const id = this.dataSource.propertyAdd(position, ops); if (!id) { return; } const property = this.propertyGetOrCreate(id); property.move(position); return id; } abstract propertyGetOrCreate(propertyId: string): Property; rowAdd(insertPosition: InsertToPosition | number): string { this.lockRows(false); return this.dataSource.rowAdd(insertPosition); } rowGetOrCreate(rowId: string): Row { return new RowBase(this, rowId); } protected rowsMapping(rows: Row[]): Row[] { return this.searchRowsMapping(rows, this.searchString.value); } setSearch(str: string): void { this.searchString.value = str; } traitGet(key: TraitKey): T | undefined { return this.traitMap.get(key.key) as T | undefined; } protected traitSet(key: TraitKey, value: T): T { this.traitMap.set(key.key, value); return value; } }