Files
AFFiNE-Mirror/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts
T
zzj3720 6689bd1914 feat(editor): add created-time and created-by property for database block (#12156)
close: BS-3431

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added "Created By" property type and cell renderer, displaying creator's avatar and name in database blocks.
  - Introduced "Created Time" property type and cell renderer, showing formatted creation timestamps.

- **Improvements**
  - Enhanced table and Kanban views with improved column and row movement, hiding, and statistics capabilities.
  - Streamlined property and row management with unified object handling and reactive signals for better performance and reliability.
  - Improved avatar display logic to handle removed or unnamed users gracefully.
  - Refactored property and row APIs to consolidate access patterns and support reactive updates.
  - Updated icon retrieval and reactive value access for improved UI responsiveness in database and Kanban cells.
  - Consolidated property and cell access methods to use "get or create" patterns ensuring consistent data availability.
  - Added locking mechanism to stabilize computed signals during locked states.
  - Modularized table and Kanban column and row abstractions for better encapsulation and maintainability.

- **Bug Fixes**
  - Corrected row and column deletion, movement, and selection behaviors across table and Kanban views.
  - Fixed issues with property and row referencing, ensuring consistent handling of identifiers and objects.
  - Removed debugging logs and fixed method calls to align with updated APIs.

- **Style**
  - Updated and simplified table column header styles for a cleaner appearance.

- **Chores**
  - Added `@emotion/css` dependency for styling support.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-08 11:35:37 +00:00

188 lines
4.7 KiB
TypeScript

// related component
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { ShadowlessElement } from '@blocksuite/std';
import { signal } from '@preact/signals-core';
import { css } from 'lit';
import { property, state } from 'lit/decorators.js';
import { html } from 'lit/static-html.js';
import type {
CellRenderProps,
DataViewCellLifeCycle,
} from '../../../core/property/index.js';
import { renderUniLit } from '../../../core/utils/uni-component/uni-component.js';
import type { Property } from '../../../core/view-manager/property.js';
import type { KanbanSingleView } from '../kanban-view-manager.js';
import type { KanbanViewSelection } from '../selection';
const styles = css`
affine-data-view-kanban-cell {
border-radius: 4px;
display: flex;
align-items: center;
padding: 4px;
min-height: 20px;
border: 1px solid transparent;
box-sizing: border-box;
}
affine-data-view-kanban-cell:hover {
background-color: var(--affine-hover-color);
}
affine-data-view-kanban-cell .icon {
display: flex;
align-items: center;
justify-content: center;
align-self: start;
margin-right: 12px;
height: var(--data-view-cell-text-line-height);
}
affine-data-view-kanban-cell .icon svg {
width: 16px;
height: 16px;
fill: var(--affine-icon-color);
color: var(--affine-icon-color);
}
.kanban-cell {
flex: 1;
display: block;
width: 196px;
}
`;
export class KanbanCell extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = styles;
private readonly _cell = signal<DataViewCellLifeCycle>();
selectCurrentCell = (editing: boolean) => {
const selectionView = this.closest(
'affine-data-view-kanban'
)?.selectionController;
if (!selectionView) return;
if (selectionView) {
const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) {
selectionView.selection = {
selectionType: 'cell',
groupKey: this.groupKey,
cardId: this.cardId,
columnId: this.column.id,
isEditing: true,
};
} else {
selectionView.selection = {
selectionType: 'cell',
groupKey: this.groupKey,
cardId: this.cardId,
columnId: this.column.id,
isEditing: false,
};
}
}
};
get cell(): DataViewCellLifeCycle | undefined {
return this._cell.value;
}
get selection() {
return this.closest('affine-data-view-kanban')?.selectionController;
}
override connectedCallback() {
super.connectedCallback();
this._disposables.addFromEvent(this, 'click', e => {
if (e.shiftKey) {
return;
}
e.stopPropagation();
const selectionElement = this.closest(
'affine-data-view-kanban'
)?.selectionController;
if (!selectionElement) return;
if (e.shiftKey) return;
if (!this.isEditing$.value) {
this.selectCurrentCell(!this.column.readonly$.value);
}
});
}
isSelected(selection: KanbanViewSelection) {
if (
selection.selectionType !== 'cell' ||
selection.groupKey !== this.groupKey
) {
return;
}
return (
selection.cardId === this.cardId && selection.columnId === this.column.id
);
}
override render() {
const props: CellRenderProps = {
cell: this.column.cellGetOrCreate(this.cardId),
isEditing$: this.isEditing$,
selectCurrentCell: this.selectCurrentCell,
};
const renderer = this.column.renderer$.value;
if (!renderer) return;
const { view } = renderer;
this.view.lockRows(this.isEditing$.value);
this.dataset['editing'] = `${this.isEditing$.value}`;
this.style.border = this.isFocus
? '1px solid var(--affine-primary-color)'
: '';
this.style.boxShadow = this.isEditing$.value
? '0px 0px 0px 2px rgba(30, 150, 235, 0.30)'
: '';
return html` ${this.renderIcon()}
${renderUniLit(view, props, {
ref: this._cell,
class: 'kanban-cell',
style: { display: 'block', flex: '1', overflow: 'hidden' },
})}`;
}
renderIcon() {
if (this.contentOnly) {
return;
}
return html` <uni-lit class="icon" .uni="${this.column.icon}"></uni-lit>`;
}
@property({ attribute: false })
accessor cardId!: string;
@property({ attribute: false })
accessor column!: Property;
@property({ attribute: false })
accessor contentOnly = false;
isEditing$ = signal(false);
@property({ attribute: false })
accessor groupKey!: string;
@state()
accessor isFocus = false;
@property({ attribute: false })
accessor view!: KanbanSingleView;
}
declare global {
interface HTMLElementTagNameMap {
'affine-data-view-kanban-cell': KanbanCell;
}
}