refactor(editor): move database selection into the corresponding view (#9752)

This commit is contained in:
zzj3720
2025-01-17 09:03:13 +00:00
parent 338ccb427b
commit aa21ac6d64
34 changed files with 375 additions and 394 deletions

View File

@@ -5,5 +5,4 @@ export * from '../group-by/matcher.js';
export type { GroupByConfig } from '../group-by/types.js';
export type { GroupRenderProps } from '../group-by/types.js';
export * from './css-variable.js';
export * from './selection-schema.js';
export * from './types.js';

View File

@@ -1,133 +0,0 @@
import { BaseSelection, SelectionExtension } from '@blocksuite/store';
import { z } from 'zod';
import type { DataViewSelection, GetDataViewSelection } from '../types.js';
const TableViewSelectionSchema = z.union([
z.object({
viewId: z.string(),
type: z.literal('table'),
selectionType: z.literal('area'),
rowsSelection: z.object({
start: z.number(),
end: z.number(),
}),
columnsSelection: z.object({
start: z.number(),
end: z.number(),
}),
focus: z.object({
rowIndex: z.number(),
columnIndex: z.number(),
}),
isEditing: z.boolean(),
}),
z.object({
viewId: z.string(),
type: z.literal('table'),
selectionType: z.literal('row'),
rows: z.array(
z.object({ id: z.string(), groupKey: z.string().optional() })
),
}),
]);
const KanbanCellSelectionSchema = z.object({
selectionType: z.literal('cell'),
groupKey: z.string(),
cardId: z.string(),
columnId: z.string(),
isEditing: z.boolean(),
});
const KanbanCardSelectionSchema = z.object({
selectionType: z.literal('card'),
cards: z.array(
z.object({
groupKey: z.string(),
cardId: z.string(),
})
),
});
const KanbanGroupSelectionSchema = z.object({
selectionType: z.literal('group'),
groupKeys: z.array(z.string()),
});
const DatabaseSelectionSchema = z.object({
blockId: z.string(),
viewSelection: z.union([
TableViewSelectionSchema,
KanbanCellSelectionSchema,
KanbanCardSelectionSchema,
KanbanGroupSelectionSchema,
]),
});
export class DatabaseSelection extends BaseSelection {
static override group = 'note';
static override type = 'database';
readonly viewSelection: DataViewSelection;
get viewId() {
return this.viewSelection.viewId;
}
constructor({
blockId,
viewSelection,
}: {
blockId: string;
viewSelection: DataViewSelection;
}) {
super({
blockId,
});
this.viewSelection = viewSelection;
}
static override fromJSON(json: Record<string, unknown>): DatabaseSelection {
DatabaseSelectionSchema.parse(json);
return new DatabaseSelection({
blockId: json.blockId as string,
viewSelection: json.viewSelection as DataViewSelection,
});
}
override equals(other: BaseSelection): boolean {
if (!(other instanceof DatabaseSelection)) {
return false;
}
return this.blockId === other.blockId;
}
getSelection<T extends DataViewSelection['type']>(
type: T
): GetDataViewSelection<T> | undefined {
return this.viewSelection.type === type
? (this.viewSelection as GetDataViewSelection<T>)
: undefined;
}
override toJSON(): Record<string, unknown> {
return {
type: 'database',
blockId: this.blockId,
viewSelection: this.viewSelection,
};
}
}
declare global {
namespace BlockSuite {
interface Selection {
database: typeof DatabaseSelection;
}
}
}
export const DatabaseSelectionExtension = SelectionExtension(DatabaseSelection);

View File

@@ -1,6 +1,6 @@
import type { KanbanCardSelection } from '../../view-presets';
import type { KanbanCard } from '../../view-presets/kanban/pc/card.js';
import { KanbanCell } from '../../view-presets/kanban/pc/cell.js';
import type { KanbanCardSelection } from '../../view-presets/kanban/types.js';
import type { RecordDetail } from './detail.js';
import { RecordField } from './field.js';

View File

@@ -1,17 +1,11 @@
import type { KanbanViewSelectionWithType } from '../view-presets/kanban/types.js';
import type { TableViewSelectionWithType } from '../view-presets/table/types.js';
import type {
KanbanViewSelectionWithType,
TableViewSelectionWithType,
} from '../view-presets';
export type DataViewSelection =
| TableViewSelectionWithType
| KanbanViewSelectionWithType;
export type GetDataViewSelection<
K extends DataViewSelection['type'],
T = DataViewSelection,
> = T extends {
type: K;
}
? T
: never;
export type DataViewSelectionState = DataViewSelection | undefined;
export type PropertyDataUpdater<
Data extends Record<string, unknown> = Record<string, unknown>,
@@ -20,7 +14,3 @@ export type PropertyDataUpdater<
export interface DatabaseFlags {
enable_number_formatting: boolean;
}
export const defaultDatabaseFlags: Readonly<DatabaseFlags> = {
enable_number_formatting: false,
};

View File

@@ -2,3 +2,4 @@ export * from './define.js';
export * from './kanban-view-manager.js';
export * from './pc/kanban-view.js';
export * from './renderer.js';
export * from './selection.js';

View File

@@ -14,7 +14,7 @@ import { type DataViewInstance, renderUniLit } from '../../../core/index.js';
import { sortable } from '../../../core/utils/wc-dnd/sort/sort-context.js';
import { DataViewBase } from '../../../core/view/data-view-base.js';
import type { KanbanSingleView } from '../kanban-view-manager.js';
import type { KanbanViewSelectionWithType } from '../types.js';
import type { KanbanViewSelectionWithType } from '../selection';
const styles = css`
mobile-data-view-kanban {

View File

@@ -14,7 +14,7 @@ import type {
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 '../types.js';
import type { KanbanViewSelection } from '../selection';
const styles = css`
affine-data-view-kanban-cell {

View File

@@ -1,7 +1,7 @@
import type { UIEventStateContext } from '@blocksuite/block-std';
import type { ReactiveController } from 'lit';
import type { KanbanViewSelectionWithType } from '../../types.js';
import type { KanbanViewSelectionWithType } from '../../selection';
import type { DataViewKanban } from '../kanban-view.js';
export class KanbanClipboardController implements ReactiveController {

View File

@@ -9,7 +9,7 @@ import type {
KanbanGroupSelection,
KanbanViewSelection,
KanbanViewSelectionWithType,
} from '../../types.js';
} from '../../selection';
import { KanbanCard } from '../card.js';
import { KanbanCell } from '../cell.js';
import type { KanbanGroup } from '../group.js';

View File

@@ -20,7 +20,7 @@ import {
import { horizontalListSortingStrategy } from '../../../core/utils/wc-dnd/sort/strategies/index.js';
import { DataViewBase } from '../../../core/view/data-view-base.js';
import type { KanbanSingleView } from '../kanban-view-manager.js';
import type { KanbanViewSelectionWithType } from '../types.js';
import type { KanbanViewSelectionWithType } from '../selection';
import { KanbanClipboardController } from './controller/clipboard.js';
import { KanbanDragController } from './controller/drag.js';
import { KanbanHotkeysController } from './controller/hotkeys.js';

View File

@@ -0,0 +1,50 @@
import { z } from 'zod';
export const KanbanViewTypeSchema = z.object({
viewId: z.string(),
type: z.literal('kanban'),
});
export const KanbanCellSelectionSchema = z.object({
selectionType: z.literal('cell'),
groupKey: z.string(),
cardId: z.string(),
columnId: z.string(),
isEditing: z.boolean(),
});
const KanbanCardSelectionCardSchema = z.object({
groupKey: z.string(),
cardId: z.string(),
});
export const KanbanCardSelectionSchema = z.object({
selectionType: z.literal('card'),
cards: z
.tuple([KanbanCardSelectionCardSchema])
.rest(KanbanCardSelectionCardSchema),
});
export const KanbanGroupSelectionSchema = z.object({
selectionType: z.literal('group'),
groupKeys: z.tuple([z.string()]).rest(z.string()),
});
export const KanbanViewSelectionSchema = z.union([
KanbanCellSelectionSchema,
KanbanCardSelectionSchema,
KanbanGroupSelectionSchema,
]);
export const KanbanViewSelectionWithTypeSchema = z.union([
z.intersection(KanbanViewTypeSchema, KanbanCellSelectionSchema),
z.intersection(KanbanViewTypeSchema, KanbanCardSelectionSchema),
z.intersection(KanbanViewTypeSchema, KanbanGroupSelectionSchema),
]);
export type KanbanCellSelection = z.TypeOf<typeof KanbanCellSelectionSchema>;
export type KanbanCardSelectionCard = z.TypeOf<
typeof KanbanCardSelectionCardSchema
>;
export type KanbanCardSelection = z.TypeOf<typeof KanbanCardSelectionSchema>;
export type KanbanGroupSelection = z.TypeOf<typeof KanbanGroupSelectionSchema>;
export type KanbanViewSelection = z.TypeOf<typeof KanbanViewSelectionSchema>;
export type KanbanViewSelectionWithType = z.TypeOf<
typeof KanbanViewSelectionWithTypeSchema
>;

View File

@@ -1,32 +0,0 @@
type WithKanbanViewType<T> = T extends unknown
? {
viewId: string;
type: 'kanban';
} & T
: never;
export type KanbanCellSelection = {
selectionType: 'cell';
groupKey: string;
cardId: string;
columnId: string;
isEditing: boolean;
};
export type KanbanCardSelectionCard = {
groupKey: string;
cardId: string;
};
export type KanbanCardSelection = {
selectionType: 'card';
cards: [KanbanCardSelectionCard, ...KanbanCardSelectionCard[]];
};
export type KanbanGroupSelection = {
selectionType: 'group';
groupKeys: [string, ...string[]];
};
export type KanbanViewSelection =
| KanbanCellSelection
| KanbanCardSelection
| KanbanGroupSelection;
export type KanbanViewSelectionWithType =
WithKanbanViewType<KanbanViewSelection>;

View File

@@ -1,4 +1,5 @@
export * from './define.js';
export * from './pc/table-view.js';
export * from './renderer.js';
export * from './selection.js';
export * from './table-view-manager.js';

View File

@@ -11,8 +11,8 @@ import {
renderUniLit,
type SingleView,
} from '../../../core/index.js';
import { TableViewAreaSelection } from '../selection';
import type { TableColumn } from '../table-view-manager.js';
import { TableAreaSelection } from '../types.js';
export class MobileTableCell extends SignalWatcher(
WithDisposable(ShadowlessElement)
@@ -78,7 +78,7 @@ export class MobileTableCell extends SignalWatcher(
setSelection({
viewId,
type: 'table',
...TableAreaSelection.create({
...TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {
rowIndex: this.rowIndex,

View File

@@ -15,8 +15,8 @@ import { GroupTitle } from '../../../core/group-by/group-title.js';
import type { GroupData } from '../../../core/group-by/trait.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';
import { TableAreaSelection } from '../types.js';
const styles = css`
.data-view-table-group-add-row {
@@ -57,7 +57,7 @@ export class MobileTableGroup extends SignalWatcher(
const index = this.view.properties$.value.findIndex(
v => v.type$.value === 'title'
);
selectionController.selection = TableAreaSelection.create({
selectionController.selection = TableViewAreaSelection.create({
groupKey: this.group?.key,
focus: {
rowIndex: this.rows.length - 1,
@@ -75,7 +75,7 @@ export class MobileTableGroup extends SignalWatcher(
const index = this.view.properties$.value.findIndex(
v => v.type$.value === 'title'
);
selectionController.selection = TableAreaSelection.create({
selectionController.selection = TableViewAreaSelection.create({
groupKey: this.group?.key,
focus: {
rowIndex: 0,

View File

@@ -16,8 +16,8 @@ import type { DataViewInstance } from '../../../core/index.js';
import { renderUniLit } from '../../../core/utils/uni-component/uni-component.js';
import { DataViewBase } from '../../../core/view/data-view-base.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
import type { TableViewSelectionWithType } from '../selection';
import type { TableSingleView } from '../table-view-manager.js';
import type { TableViewSelectionWithType } from '../types.js';
export class MobileDataViewTable extends DataViewBase<
TableSingleView,

View File

@@ -15,11 +15,11 @@ import type {
DataViewCellLifeCycle,
} from '../../../core/property/index.js';
import type { SingleView } from '../../../core/view-manager/single-view.js';
import type { TableColumn } from '../table-view-manager.js';
import {
TableAreaSelection,
TableViewAreaSelection,
type TableViewSelectionWithType,
} from '../types.js';
} from '../selection';
import type { TableColumn } from '../table-view-manager.js';
export class DatabaseCellContainer extends SignalWatcher(
WithDisposable(ShadowlessElement)
@@ -63,7 +63,7 @@ export class DatabaseCellContainer extends SignalWatcher(
if (selectionView) {
const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) {
selectionView.selection = TableAreaSelection.create({
selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {
rowIndex: this.rowIndex,
@@ -72,7 +72,7 @@ export class DatabaseCellContainer extends SignalWatcher(
isEditing: true,
});
} else {
selectionView.selection = TableAreaSelection.create({
selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {
rowIndex: this.rowIndex,

View File

@@ -4,11 +4,11 @@ import type { ReactiveController } from 'lit';
import type { Cell } from '../../../../core/view-manager/cell.js';
import type { Row } from '../../../../core/view-manager/row.js';
import {
TableAreaSelection,
TableRowSelection,
TableViewAreaSelection,
TableViewRowSelection,
type TableViewSelection,
type TableViewSelectionWithType,
} from '../../types.js';
} from '../../selection';
import type { DataViewTable } from '../table-view.js';
const BLOCKSUITE_DATABASE_TABLE = 'blocksuite/database/table';
@@ -85,7 +85,7 @@ export class TableClipboardController implements ReactiveController {
if (!clipboardData) return;
const tableSelection = this.host.selectionController.selection;
if (TableRowSelection.is(tableSelection)) {
if (TableViewRowSelection.is(tableSelection)) {
return;
}
if (tableSelection) {
@@ -196,8 +196,8 @@ function getSelectedArea(
table: DataViewTable
): SelectedArea | undefined {
const view = table.props.view;
if (TableRowSelection.is(selection)) {
const rows = TableRowSelection.rows(selection)
if (TableViewRowSelection.is(selection)) {
const rows = TableViewRowSelection.rows(selection)
.map(row => {
const y =
table.selectionController
@@ -255,11 +255,11 @@ type SelectedArea = {
}[];
function getTargetRangeFromSelection(
selection: TableAreaSelection,
selection: TableViewAreaSelection,
data: JsonAreaData
) {
const { rowsSelection, columnsSelection, focus } = selection;
return TableAreaSelection.isFocus(selection)
return TableViewAreaSelection.isFocus(selection)
? {
row: {
start: focus.rowIndex,
@@ -285,7 +285,7 @@ function getTargetRangeFromSelection(
function pasteToCells(
table: DataViewTable,
rows: JsonAreaData,
selection: TableAreaSelection
selection: TableViewAreaSelection
) {
const srcRowLength = rows.length;
const srcColumnLength = rows[0]?.length ?? 0;

View File

@@ -7,7 +7,7 @@ import { createRef, ref } from 'lit/directives/ref.js';
import * as Y from 'yjs';
import { t } from '../../../../core/index.js';
import type { TableAreaSelection } from '../../types.js';
import type { TableViewAreaSelection } from '../../selection';
import type { DataViewTable } from '../table-view.js';
export class DragToFillElement extends ShadowlessElement {
@@ -50,7 +50,7 @@ export class DragToFillElement extends ShadowlessElement {
export function fillSelectionWithFocusCellData(
host: DataViewTable,
selection: TableAreaSelection
selection: TableViewAreaSelection
) {
const { groupKey, rowsSelection, columnsSelection, focus } = selection;

View File

@@ -1,7 +1,7 @@
import { popupTargetFromElement } from '@blocksuite/affine-components/context-menu';
import type { ReactiveController } from 'lit';
import { TableAreaSelection, TableRowSelection } from '../../types.js';
import { TableViewAreaSelection, TableViewRowSelection } from '../../selection';
import { popRowMenu } from '../menu.js';
import type { DataViewTable } from '../table-view.js';
@@ -22,8 +22,8 @@ export class TableHotkeysController implements ReactiveController {
if (!selection) {
return;
}
if (TableRowSelection.is(selection)) {
const rows = TableRowSelection.rowsIds(selection);
if (TableViewRowSelection.is(selection)) {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.host.props.view.rowDelete(rows);
return;
@@ -73,23 +73,24 @@ export class TableHotkeysController implements ReactiveController {
if (!selection) {
return false;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
const result = this.selectionController.rowsToArea(
selection.rows.map(v => v.id)
);
if (result) {
this.selectionController.selection = TableAreaSelection.create({
groupKey: result.groupKey,
focus: {
rowIndex: result.start,
columnIndex: 0,
},
rowsSelection: {
start: result.start,
end: result.end,
},
isEditing: false,
});
this.selectionController.selection =
TableViewAreaSelection.create({
groupKey: result.groupKey,
focus: {
rowIndex: result.start,
columnIndex: 0,
},
rowsSelection: {
start: result.start,
end: result.end,
},
isEditing: false,
});
} else {
this.selectionController.selection = undefined;
}
@@ -112,23 +113,24 @@ export class TableHotkeysController implements ReactiveController {
if (!selection) {
return false;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
const result = this.selectionController.rowsToArea(
selection.rows.map(v => v.id)
);
if (result) {
this.selectionController.selection = TableAreaSelection.create({
groupKey: result.groupKey,
focus: {
rowIndex: result.start,
columnIndex: 0,
},
rowsSelection: {
start: result.start,
end: result.end,
},
isEditing: false,
});
this.selectionController.selection =
TableViewAreaSelection.create({
groupKey: result.groupKey,
focus: {
rowIndex: result.start,
columnIndex: 0,
},
rowsSelection: {
start: result.start,
end: result.end,
},
isEditing: false,
});
}
} else if (selection.isEditing) {
return false;
@@ -145,7 +147,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing
) {
return false;
@@ -167,7 +169,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing
) {
return false;
@@ -180,7 +182,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing
) {
return false;
@@ -193,7 +195,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing
) {
return false;
@@ -206,7 +208,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing
) {
return false;
@@ -221,7 +223,7 @@ export class TableHotkeysController implements ReactiveController {
return false;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
this.selectionController.navigateRowSelection('up', false);
} else if (selection.isEditing) {
return false;
@@ -238,7 +240,7 @@ export class TableHotkeysController implements ReactiveController {
return false;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
this.selectionController.navigateRowSelection('down', false);
} else if (selection.isEditing) {
return false;
@@ -256,7 +258,7 @@ export class TableHotkeysController implements ReactiveController {
return false;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
this.selectionController.navigateRowSelection('up', true);
} else if (selection.isEditing) {
return false;
@@ -274,7 +276,7 @@ export class TableHotkeysController implements ReactiveController {
return false;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
this.selectionController.navigateRowSelection('down', true);
} else if (selection.isEditing) {
return false;
@@ -290,7 +292,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing ||
this.selectionController.isRowSelection()
) {
@@ -307,7 +309,7 @@ export class TableHotkeysController implements ReactiveController {
const selection = this.selectionController.selection;
if (
!selection ||
TableRowSelection.is(selection) ||
TableViewRowSelection.is(selection) ||
selection.isEditing ||
this.selectionController.isRowSelection()
) {
@@ -322,7 +324,7 @@ export class TableHotkeysController implements ReactiveController {
'Mod-a': context => {
const selection = this.selectionController.selection;
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
return false;
}
if (selection?.isEditing) {
@@ -330,7 +332,7 @@ export class TableHotkeysController implements ReactiveController {
}
if (selection) {
context.get('keyboardState').raw.preventDefault();
this.selectionController.selection = TableRowSelection.create({
this.selectionController.selection = TableViewRowSelection.create({
rows:
this.host.props.view.groupTrait.groupsDataList$.value?.flatMap(
group =>
@@ -350,7 +352,7 @@ export class TableHotkeysController implements ReactiveController {
if (!selection) {
return;
}
if (TableRowSelection.is(selection)) {
if (TableViewRowSelection.is(selection)) {
// open multi-rows context-menu
return;
}
@@ -368,7 +370,7 @@ export class TableHotkeysController implements ReactiveController {
id: cell.rowId,
groupKey: selection.groupKey,
};
this.selectionController.selection = TableRowSelection.create({
this.selectionController.selection = TableViewRowSelection.create({
rows: [row],
});
popRowMenu(

View File

@@ -13,11 +13,11 @@ import {
type CellFocus,
type MultiSelection,
RowWithGroup,
TableAreaSelection,
TableRowSelection,
TableViewAreaSelection,
TableViewRowSelection,
type TableViewSelection,
type TableViewSelectionWithType,
} from '../../types.js';
} from '../../selection';
import type { DatabaseCellContainer } from '../cell.js';
import type { TableRow } from '../row/row.js';
import type { DataViewTable } from '../table-view.js';
@@ -240,7 +240,7 @@ export class TableSelectionController implements ReactiveController {
const index = this.host.props.view.properties$.value.findIndex(
v => v.type$.value === 'title'
);
this.selection = TableAreaSelection.create({
this.selection = TableViewAreaSelection.create({
groupKey: groupKey,
focus: {
rowIndex: rows?.findIndex(v => v === id) ?? 0,
@@ -279,7 +279,7 @@ export class TableSelectionController implements ReactiveController {
});
}
areaToRows(selection: TableAreaSelection) {
areaToRows(selection: TableViewAreaSelection) {
const rows = this.rows(selection.groupKey) ?? [];
const ids = Array.from({
length: selection.rowsSelection.end - selection.rowsSelection.start + 1,
@@ -350,7 +350,7 @@ export class TableSelectionController implements ReactiveController {
}
focusFirstCell() {
this.selection = TableAreaSelection.create({
this.selection = TableViewAreaSelection.create({
focus: {
rowIndex: 0,
columnIndex: 0,
@@ -359,7 +359,7 @@ export class TableSelectionController implements ReactiveController {
});
}
focusToArea(selection: TableAreaSelection) {
focusToArea(selection: TableViewAreaSelection) {
return {
...selection,
rowsSelection: selection.rowsSelection ?? {
@@ -371,7 +371,7 @@ export class TableSelectionController implements ReactiveController {
end: selection.focus.columnIndex,
},
isEditing: false,
} satisfies TableAreaSelection;
} satisfies TableViewAreaSelection;
}
focusToCell(position: 'left' | 'right' | 'up' | 'down') {
@@ -544,7 +544,7 @@ export class TableSelectionController implements ReactiveController {
}
navigateRowSelection(direction: 'up' | 'down', append = false) {
if (!TableRowSelection.is(this.selection)) return;
if (!TableViewRowSelection.is(this.selection)) return;
const rows = this.selection.rows;
const lastRow = rows[rows.length - 1];
if (!lastRow) return;
@@ -593,7 +593,7 @@ export class TableSelectionController implements ReactiveController {
} else {
const target = direction === 'up' ? prevRow : nextRow;
if (target != null) {
this.selection = TableRowSelection.create({
this.selection = TableViewRowSelection.create({
rows: [target],
});
}
@@ -619,7 +619,7 @@ export class TableSelectionController implements ReactiveController {
}) {
const key = (r: RowWithGroup) => `${r.id}.${r.groupKey ? r.groupKey : ''}`;
const rows = new Set(
TableRowSelection.rows(this.selection).map(r => key(r))
TableViewRowSelection.rows(this.selection).map(r => key(r))
);
remove.forEach(row => rows.delete(key(row)));
add.forEach(row => rows.add(key(row)));
@@ -634,7 +634,7 @@ export class TableSelectionController implements ReactiveController {
},
];
});
this.selection = TableRowSelection.create({
this.selection = TableViewRowSelection.create({
rows: result,
});
}
@@ -826,7 +826,7 @@ export class TableSelectionController implements ReactiveController {
row: MultiSelection;
column: MultiSelection;
}) => {
this.selection = TableAreaSelection.create({
this.selection = TableViewAreaSelection.create({
groupKey: groupKey,
rowsSelection: selection.row,
columnsSelection: selection.column,
@@ -881,7 +881,7 @@ export class TableSelectionController implements ReactiveController {
this.__dragToFillElement.dragging = false;
fillSelectionWithFocusCellData(
this.host,
TableAreaSelection.create({
TableViewAreaSelection.create({
groupKey: groupKey,
rowsSelection: selection.row,
columnsSelection: selection.column,
@@ -921,7 +921,7 @@ export class TableSelectionController implements ReactiveController {
id: rowId,
groupKey,
};
const isSelected = TableRowSelection.includes(this.selection, row);
const isSelected = TableViewRowSelection.includes(this.selection, row);
this.rowSelectionChange({
add: isSelected ? [] : [row],
remove: isSelected ? [row] : [],

View File

@@ -18,8 +18,8 @@ import { createDndContext } from '../../../core/utils/wc-dnd/dnd-context.js';
import { defaultActivators } from '../../../core/utils/wc-dnd/sensors/index.js';
import { linearMove } from '../../../core/utils/wc-dnd/utils/linear-move.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
import { TableViewAreaSelection } from '../selection';
import type { TableSingleView } from '../table-view-manager.js';
import { TableAreaSelection } from '../types.js';
import { DataViewColumnPreview } from './header/column-renderer.js';
import { getVerticalIndicator } from './header/vertical-indicator.js';
import type { DataViewTable } from './table-view.js';
@@ -74,7 +74,7 @@ export class TableGroup extends SignalWatcher(
const index = this.view.properties$.value.findIndex(
v => v.type$.value === 'title'
);
selectionController.selection = TableAreaSelection.create({
selectionController.selection = TableViewAreaSelection.create({
groupKey: this.group?.key,
focus: {
rowIndex: this.rows.length - 1,
@@ -92,7 +92,7 @@ export class TableGroup extends SignalWatcher(
const index = this.view.properties$.value.findIndex(
v => v.type$.value === 'title'
);
selectionController.selection = TableAreaSelection.create({
selectionController.selection = TableViewAreaSelection.create({
groupKey: this.group?.key,
focus: {
rowIndex: 0,

View File

@@ -13,7 +13,7 @@ import {
import { html } from 'lit';
import type { DataViewRenderer } from '../../../core/data-view.js';
import { TableRowSelection } from '../types.js';
import { TableViewRowSelection } from '../selection';
import type { TableSelectionController } from './controller/selection.js';
export const openDetail = (
@@ -38,11 +38,11 @@ export const popRowMenu = (
selectionController: TableSelectionController
) => {
const selection = selectionController.selection;
if (!TableRowSelection.is(selection)) {
if (!TableViewRowSelection.is(selection)) {
return;
}
if (selection.rows.length > 1) {
const rows = TableRowSelection.rowsIds(selection);
const rows = TableViewRowSelection.rowsIds(selection);
popFilterableSimpleMenu(ele, [
menu.group({
name: '',

View File

@@ -7,9 +7,9 @@ import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import {
TableRowSelection,
TableViewRowSelection,
type TableViewSelectionWithType,
} from '../../types.js';
} from '../../selection';
export class RowSelectCheckbox extends SignalWatcher(
WithDisposable(ShadowlessElement)
@@ -49,7 +49,7 @@ export class RowSelectCheckbox extends SignalWatcher(
if (!selection || selection.selectionType !== 'row') {
return false;
}
return TableRowSelection.includes(selection, {
return TableViewRowSelection.includes(selection, {
id: this.rowId,
groupKey: this.groupKey,
});

View File

@@ -9,8 +9,11 @@ import { styleMap } from 'lit/directives/style-map.js';
import { html } from 'lit/static-html.js';
import type { DataViewRenderer } from '../../../../core/data-view.js';
import {
TableViewRowSelection,
type TableViewSelection,
} from '../../selection';
import type { TableSingleView } from '../../table-view-manager.js';
import { TableRowSelection, type TableViewSelection } from '../../types.js';
import { openDetail, popRowMenu } from '../menu.js';
export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) {
@@ -133,8 +136,8 @@ export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) {
const ele = e.target as HTMLElement;
const cell = ele.closest('affine-database-cell-container');
const row = { id: this.rowId, groupKey: this.groupKey };
if (!TableRowSelection.includes(selection.selection, row)) {
selection.selection = TableRowSelection.create({
if (!TableViewRowSelection.includes(selection.selection, row)) {
selection.selection = TableViewRowSelection.create({
rows: [row],
});
}
@@ -201,7 +204,7 @@ export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) {
return;
}
this.setSelection(
TableRowSelection.create({
TableViewRowSelection.create({
rows: [{ id: this.rowId, groupKey: this.groupKey }],
})
);
@@ -214,14 +217,14 @@ export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) {
const ele = e.currentTarget as HTMLElement;
const selection = this.selectionController.selection;
if (
!TableRowSelection.is(selection) ||
!TableViewRowSelection.is(selection) ||
!selection.rows.some(
row => row.id === this.rowId && row.groupKey === this.groupKey
)
) {
const row = { id: this.rowId, groupKey: this.groupKey };
this.setSelection(
TableRowSelection.create({
TableViewRowSelection.create({
rows: [row],
})
);

View File

@@ -14,8 +14,8 @@ import type { DataViewInstance } from '../../../core/index.js';
import { renderUniLit } from '../../../core/utils/uni-component/uni-component.js';
import { DataViewBase } from '../../../core/view/data-view-base.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
import type { TableViewSelectionWithType } from '../selection';
import type { TableSingleView } from '../table-view-manager.js';
import type { TableViewSelectionWithType } from '../types.js';
import { TableClipboardController } from './controller/clipboard.js';
import { TableDragController } from './controller/drag.js';
import { TableHotkeysController } from './controller/hotkeys.js';

View File

@@ -0,0 +1,123 @@
import { z } from 'zod';
export const TableViewTypeSchema = z.object({
viewId: z.string(),
type: z.literal('table'),
});
export const RangeSchema = z.object({
start: z.number(),
end: z.number(),
});
export const FocusSchema = z.object({
rowIndex: z.number(),
columnIndex: z.number(),
});
export const TableViewAreaSelectionSchema = z.object({
selectionType: z.literal('area'),
groupKey: z.string().optional(),
rowsSelection: RangeSchema,
columnsSelection: RangeSchema,
focus: FocusSchema,
isEditing: z.boolean(),
});
export const RowWithGroupSchema = z.object({
id: z.string(),
groupKey: z.string().optional(),
});
export const TableViewRowSelectionSchema = z.object({
selectionType: z.literal('row'),
rows: z.array(RowWithGroupSchema),
});
export const TableViewSelectionSchema = z.union([
TableViewAreaSelectionSchema,
TableViewRowSelectionSchema,
]);
export const TableViewSelectionWithTypeSchema = z.union([
z.intersection(TableViewTypeSchema, TableViewAreaSelectionSchema),
z.intersection(TableViewTypeSchema, TableViewRowSelectionSchema),
]);
export type RowWithGroup = z.TypeOf<typeof RowWithGroupSchema>;
export const RowWithGroup = {
equal(a?: RowWithGroup, b?: RowWithGroup) {
if (a == null || b == null) {
return false;
}
return a.id === b.id && a.groupKey === b.groupKey;
},
};
export type TableViewRowSelection = z.TypeOf<
typeof TableViewRowSelectionSchema
>;
export const TableViewRowSelection = {
rows: (selection?: TableViewSelection): RowWithGroup[] => {
if (selection?.selectionType === 'row') {
return selection.rows;
}
return [];
},
rowsIds: (selection?: TableViewSelection): string[] => {
return TableViewRowSelection.rows(selection).map(v => v.id);
},
includes(
selection: TableViewSelection | undefined,
row: RowWithGroup
): boolean {
if (!selection) {
return false;
}
return TableViewRowSelection.rows(selection).some(v =>
RowWithGroup.equal(v, row)
);
},
create(options: { rows: RowWithGroup[] }): TableViewRowSelection {
return {
selectionType: 'row',
rows: options.rows,
};
},
is(selection?: TableViewSelection): selection is TableViewRowSelection {
return selection?.selectionType === 'row';
},
};
export type TableViewAreaSelection = z.TypeOf<
typeof TableViewAreaSelectionSchema
>;
export const TableViewAreaSelection = {
create: (options: {
groupKey?: string;
focus: CellFocus;
rowsSelection?: MultiSelection;
columnsSelection?: MultiSelection;
isEditing: boolean;
}): TableViewAreaSelection => {
return {
...options,
selectionType: 'area',
rowsSelection: options.rowsSelection ?? {
start: options.focus.rowIndex,
end: options.focus.rowIndex,
},
columnsSelection: options.columnsSelection ?? {
start: options.focus.columnIndex,
end: options.focus.columnIndex,
},
};
},
isFocus(selection: TableViewAreaSelection) {
return (
selection.focus.rowIndex === selection.rowsSelection.start &&
selection.focus.rowIndex === selection.rowsSelection.end &&
selection.focus.columnIndex === selection.columnsSelection.start &&
selection.focus.columnIndex === selection.columnsSelection.end
);
},
};
export type CellFocus = z.TypeOf<typeof FocusSchema>;
export type MultiSelection = z.TypeOf<typeof RangeSchema>;
export type TableViewSelection = z.TypeOf<typeof TableViewSelectionSchema>;
export type TableViewSelectionWithType = z.TypeOf<
typeof TableViewSelectionWithTypeSchema
>;

View File

@@ -10,108 +10,3 @@ export interface Column<
}
export type StatCalcOpType = string | undefined;
type WithTableViewType<T> = T extends unknown
? {
viewId: string;
type: 'table';
} & T
: never;
export type RowWithGroup = {
id: string;
groupKey?: string;
};
export const RowWithGroup = {
equal(a?: RowWithGroup, b?: RowWithGroup) {
if (a == null || b == null) {
return false;
}
return a.id === b.id && a.groupKey === b.groupKey;
},
};
export type TableRowSelection = {
selectionType: 'row';
rows: RowWithGroup[];
};
export const TableRowSelection = {
rows: (selection?: TableViewSelection): RowWithGroup[] => {
if (selection?.selectionType === 'row') {
return selection.rows;
}
return [];
},
rowsIds: (selection?: TableViewSelection): string[] => {
return TableRowSelection.rows(selection).map(v => v.id);
},
includes(
selection: TableViewSelection | undefined,
row: RowWithGroup
): boolean {
if (!selection) {
return false;
}
return TableRowSelection.rows(selection).some(v =>
RowWithGroup.equal(v, row)
);
},
create(options: { rows: RowWithGroup[] }): TableRowSelection {
return {
selectionType: 'row',
rows: options.rows,
};
},
is(selection?: TableViewSelection): selection is TableRowSelection {
return selection?.selectionType === 'row';
},
};
export type TableAreaSelection = {
selectionType: 'area';
groupKey?: string;
rowsSelection: MultiSelection;
columnsSelection: MultiSelection;
focus: CellFocus;
isEditing: boolean;
};
export const TableAreaSelection = {
create: (options: {
groupKey?: string;
focus: CellFocus;
rowsSelection?: MultiSelection;
columnsSelection?: MultiSelection;
isEditing: boolean;
}): TableAreaSelection => {
return {
...options,
selectionType: 'area',
rowsSelection: options.rowsSelection ?? {
start: options.focus.rowIndex,
end: options.focus.rowIndex,
},
columnsSelection: options.columnsSelection ?? {
start: options.focus.columnIndex,
end: options.focus.columnIndex,
},
};
},
isFocus(selection: TableAreaSelection) {
return (
selection.focus.rowIndex === selection.rowsSelection.start &&
selection.focus.rowIndex === selection.rowsSelection.end &&
selection.focus.columnIndex === selection.columnsSelection.start &&
selection.focus.columnIndex === selection.columnsSelection.end
);
},
};
export type CellFocus = {
rowIndex: number;
columnIndex: number;
};
export type MultiSelection = {
start: number;
end: number;
};
export type TableViewSelection = TableAreaSelection | TableRowSelection;
export type TableViewSelectionWithType = WithTableViewType<
TableAreaSelection | TableRowSelection
>;