mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-08 04:36:13 +08:00
6689bd1914
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 -->
188 lines
4.7 KiB
TypeScript
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;
|
|
}
|
|
}
|