mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-23 01:07:12 +08:00
close: BS-3433 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced advanced group-by configurations for database blocks with user membership support. - Added a React hook for fetching and displaying user information in member-related components. - Enabled dynamic user and membership data types in database properties. - **Improvements** - Replaced context-based service access with a dependency injection system for shared services and state. - Enhanced type safety and consistency across group-by UI components and data handling. - Centralized group data management with a new Group class and refined group trait logic. - **Bug Fixes** - Improved reliability and consistency in retrieving and rendering user and group information. - **Style** - Removed obsolete member selection styles for cleaner UI code. - **Chores** - Registered external group-by configurations via dependency injection. - Refactored internal APIs for data sources, views, and group-by matchers to use service-based patterns. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
205 lines
5.9 KiB
TypeScript
205 lines
5.9 KiB
TypeScript
import {
|
|
menu,
|
|
popFilterableSimpleMenu,
|
|
popupTargetFromElement,
|
|
} from '@blocksuite/affine-components/context-menu';
|
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
|
import { PlusIcon } from '@blocksuite/icons/lit';
|
|
import { ShadowlessElement } from '@blocksuite/std';
|
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
|
import { css, html, unsafeCSS } from 'lit';
|
|
import { property, query } from 'lit/decorators.js';
|
|
import { repeat } from 'lit/directives/repeat.js';
|
|
|
|
import type { DataViewRenderer } from '../../../core/data-view.js';
|
|
import { GroupTitle } from '../../../core/group-by/group-title.js';
|
|
import type { Group } from '../../../core/group-by/trait.js';
|
|
import type { Row } from '../../../core/index.js';
|
|
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
|
|
import type { DataViewTable } from '../pc/table-view.js';
|
|
import { TableViewAreaSelection } from '../selection';
|
|
import type { TableSingleView } from '../table-view-manager.js';
|
|
|
|
const styles = css`
|
|
.data-view-table-group-add-row {
|
|
display: flex;
|
|
width: 100%;
|
|
height: 28px;
|
|
position: relative;
|
|
z-index: 0;
|
|
cursor: pointer;
|
|
transition: opacity 0.2s ease-in-out;
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid ${unsafeCSS(cssVarV2.layer.insideBorder.border)};
|
|
}
|
|
|
|
.data-view-table-group-add-row-button {
|
|
position: sticky;
|
|
left: ${8 + LEFT_TOOL_BAR_WIDTH}px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
user-select: none;
|
|
font-size: 12px;
|
|
line-height: 20px;
|
|
color: var(--affine-text-secondary-color);
|
|
}
|
|
`;
|
|
|
|
export class MobileTableGroup extends SignalWatcher(
|
|
WithDisposable(ShadowlessElement)
|
|
) {
|
|
static override styles = styles;
|
|
|
|
private readonly clickAddRow = () => {
|
|
this.view.rowAdd('end', this.group?.key);
|
|
const selectionController = this.viewEle.selectionController;
|
|
selectionController.selection = undefined;
|
|
requestAnimationFrame(() => {
|
|
const index = this.view.properties$.value.findIndex(
|
|
v => v.type$.value === 'title'
|
|
);
|
|
selectionController.selection = TableViewAreaSelection.create({
|
|
groupKey: this.group?.key,
|
|
focus: {
|
|
rowIndex: this.rows.length - 1,
|
|
columnIndex: index,
|
|
},
|
|
isEditing: true,
|
|
});
|
|
});
|
|
};
|
|
|
|
private readonly clickAddRowInStart = () => {
|
|
this.view.rowAdd('start', this.group?.key);
|
|
const selectionController = this.viewEle.selectionController;
|
|
selectionController.selection = undefined;
|
|
requestAnimationFrame(() => {
|
|
const index = this.view.properties$.value.findIndex(
|
|
v => v.type$.value === 'title'
|
|
);
|
|
selectionController.selection = TableViewAreaSelection.create({
|
|
groupKey: this.group?.key,
|
|
focus: {
|
|
rowIndex: 0,
|
|
columnIndex: index,
|
|
},
|
|
isEditing: true,
|
|
});
|
|
});
|
|
};
|
|
|
|
private readonly clickGroupOptions = (e: MouseEvent) => {
|
|
const group = this.group;
|
|
if (!group) {
|
|
return;
|
|
}
|
|
const ele = e.currentTarget as HTMLElement;
|
|
popFilterableSimpleMenu(popupTargetFromElement(ele), [
|
|
menu.action({
|
|
name: 'Ungroup',
|
|
hide: () => group.value == null,
|
|
select: () => {
|
|
group.rows.forEach(row => {
|
|
group.manager.removeFromGroup(row.rowId, group.key);
|
|
});
|
|
},
|
|
}),
|
|
menu.action({
|
|
name: 'Delete Cards',
|
|
select: () => {
|
|
this.view.rowsDelete(group.rows.map(row => row.rowId));
|
|
},
|
|
}),
|
|
]);
|
|
};
|
|
|
|
private readonly renderGroupHeader = () => {
|
|
if (!this.group) {
|
|
return null;
|
|
}
|
|
return html`
|
|
<div
|
|
style="position: sticky;left: 0;width: max-content;padding: 6px 0;margin-bottom: 4px;display:flex;align-items:center;gap: 12px;max-width: 400px"
|
|
>
|
|
${GroupTitle(this.group, {
|
|
readonly: this.view.readonly$.value,
|
|
clickAdd: this.clickAddRowInStart,
|
|
clickOps: this.clickGroupOptions,
|
|
})}
|
|
</div>
|
|
`;
|
|
};
|
|
|
|
get rows() {
|
|
return this.group?.rows ?? this.view.rows$.value;
|
|
}
|
|
|
|
private renderRows(rows: Row[]) {
|
|
return html`
|
|
<mobile-table-header
|
|
.renderGroupHeader="${this.renderGroupHeader}"
|
|
.tableViewManager="${this.view}"
|
|
></mobile-table-header>
|
|
<div class="mobile-affine-table-body">
|
|
${repeat(
|
|
rows,
|
|
row => row.rowId,
|
|
(row, idx) => {
|
|
return html` <mobile-table-row
|
|
data-row-index="${idx}"
|
|
data-row-id="${row.rowId}"
|
|
.dataViewEle="${this.dataViewEle}"
|
|
.view="${this.view}"
|
|
.rowId="${row.rowId}"
|
|
.rowIndex="${idx}"
|
|
></mobile-table-row>`;
|
|
}
|
|
)}
|
|
</div>
|
|
${this.view.readonly$.value
|
|
? null
|
|
: html` <div
|
|
class="data-view-table-group-add-row dv-hover"
|
|
@click="${this.clickAddRow}"
|
|
>
|
|
<div
|
|
class="data-view-table-group-add-row-button dv-icon-16"
|
|
data-test-id="affine-database-add-row-button"
|
|
role="button"
|
|
>
|
|
${PlusIcon()}<span style="font-size: 12px">New Record</span>
|
|
</div>
|
|
</div>`}
|
|
<affine-database-column-stats .view="${this.view}" .group="${this.group}">
|
|
</affine-database-column-stats>
|
|
`;
|
|
}
|
|
|
|
override render() {
|
|
return this.renderRows(this.rows);
|
|
}
|
|
|
|
@property({ attribute: false })
|
|
accessor dataViewEle!: DataViewRenderer;
|
|
|
|
@property({ attribute: false })
|
|
accessor group: Group | undefined = undefined;
|
|
|
|
@query('.affine-database-block-rows')
|
|
accessor rowsContainer: HTMLElement | null = null;
|
|
|
|
@property({ attribute: false })
|
|
accessor view!: TableSingleView;
|
|
|
|
@property({ attribute: false })
|
|
accessor viewEle!: DataViewTable;
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'mobile-table-group': MobileTableGroup;
|
|
}
|
|
}
|