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 -->
This commit is contained in:
zzj3720
2025-05-08 11:35:36 +00:00
parent 7c8b977bf9
commit 6689bd1914
76 changed files with 1168 additions and 1042 deletions

View File

@@ -134,7 +134,7 @@ export class DataViewPropertiesSettingView extends SignalWatcher(
accessor view!: SingleView;
items$ = computed(() => {
return this.view.propertiesWithoutFilter$.value;
return this.view.propertiesRaw$.value.map(property => property.id);
});
renderProperty = (property: Property) => {
@@ -171,8 +171,7 @@ export class DataViewPropertiesSettingView extends SignalWatcher(
const activeIndex = properties.findIndex(id => id === activeId);
const overIndex = properties.findIndex(id => id === over.id);
this.view.propertyMove(
activeId,
this.view.propertyGetOrCreate(activeId).move(
activeIndex > overIndex
? {
before: true,
@@ -198,9 +197,7 @@ export class DataViewPropertiesSettingView extends SignalWatcher(
});
private itemsGroup() {
return this.view.propertiesWithoutFilter$.value.map(id =>
this.view.propertyGet(id)
);
return this.view.propertiesRaw$.value;
}
override connectedCallback() {
@@ -246,14 +243,12 @@ export const popPropertiesSetting = (
text: 'Properties',
onBack: props.onBack,
postfix: () => {
const items = props.view.propertiesWithoutFilter$.value.map(id =>
props.view.propertyGet(id)
);
const isAllShowed = items.every(v => !v.hide$.value);
const items = props.view.propertiesRaw$.value;
const isAllShowed = items.every(property => !property.hide$.value);
const clickChangeAll = () => {
props.view.propertiesWithoutFilter$.value.forEach(id => {
if (props.view.propertyCanHide(id)) {
props.view.propertyHideSet(id, isAllShowed);
items.forEach(property => {
if (property.hideCanSet) {
property.hideSet(isAllShowed);
}
});
};

View File

@@ -58,9 +58,7 @@ export const typeConfig = (property: Property) => {
return menu.action({
isSelected: config.type === property.type$.value,
name: config.config.name,
prefix: renderUniLit(
property.view.propertyIconGet(config.type)
),
prefix: renderUniLit(config.renderer.icon),
select: () => {
if (property.type$.value === config.type) {
return;

View File

@@ -120,7 +120,7 @@ export class RecordDetail extends SignalWatcher(
items: this.view.propertyMetas$.value.map(meta => {
return menu.action({
name: meta.config.name,
prefix: renderUniLit(this.view.propertyIconGet(meta.type)),
prefix: renderUniLit(meta.renderer.icon),
select: () => {
this.view.propertyAdd('end', meta.type);
},
@@ -136,9 +136,7 @@ export class RecordDetail extends SignalWatcher(
accessor view!: SingleView;
properties$ = computed(() => {
return this.view.detailProperties$.value.map(id =>
this.view.propertyGet(id)
);
return this.view.detailProperties$.value;
});
selection = new DetailSelection(this);
@@ -184,16 +182,20 @@ export class RecordDetail extends SignalWatcher(
this.dataset.widgetId = 'affine-detail-widget';
}
row$ = computed(() => {
return this.view.rowGetOrCreate(this.rowId);
});
hasNext() {
return this.view.rowNextGet(this.rowId) != null;
return this.row$.value.next$.value != null;
}
hasPrev() {
return this.view.rowPrevGet(this.rowId) != null;
return this.row$.value.prev$.value != null;
}
nextRow() {
const rowId = this.view.rowNextGet(this.rowId);
const rowId = this.row$.value.next$.value?.rowId;
if (rowId == null) {
return;
}
@@ -202,7 +204,7 @@ export class RecordDetail extends SignalWatcher(
}
prevRow() {
const rowId = this.view.rowPrevGet(this.rowId);
const rowId = this.row$.value.prev$.value?.rowId;
if (rowId == null) {
return;
}

View File

@@ -140,15 +140,16 @@ export class RecordField extends SignalWatcher(
${MoveLeftIcon()}
</div>`,
hide: () =>
properties.findIndex(v => v === this.column.id) === 0,
properties.findIndex(
property => property.id === this.column.id
) === 0,
select: () => {
const index = properties.findIndex(v => v === this.column.id);
const targetId = properties[index - 1];
if (!targetId) {
const prev = this.column.prev$.value;
if (!prev) {
return;
}
this.view.propertyMove(this.column.id, {
id: targetId,
this.column.move({
id: prev.id,
before: true,
});
},
@@ -161,16 +162,17 @@ export class RecordField extends SignalWatcher(
${MoveRightIcon()}
</div>`,
hide: () =>
properties.findIndex(v => v === this.column.id) ===
properties.findIndex(
property => property.id === this.column.id
) ===
properties.length - 1,
select: () => {
const index = properties.findIndex(v => v === this.column.id);
const targetId = properties[index + 1];
if (!targetId) {
const next = this.column.next$.value;
if (!next) {
return;
}
this.view.propertyMove(this.column.id, {
id: targetId,
this.column.move({
id: next.id,
before: false,
});
},
@@ -211,7 +213,7 @@ export class RecordField extends SignalWatcher(
accessor rowId!: string;
cell$ = computed(() => {
return this.column.cellGet(this.rowId);
return this.column.cellGetOrCreate(this.rowId);
});
changeEditing = (editing: boolean) => {

View File

@@ -37,7 +37,11 @@ const GroupTitleMobile = (
value: groupData.value,
data: groupData.property.data$.value,
updateData: groupData.manager.updateData,
updateValue: value => groupData.manager.updateValue(groupData.rows, value),
updateValue: value =>
groupData.manager.updateValue(
groupData.rows.map(row => row.rowId),
value
),
readonly: ops.readonly,
};
@@ -140,7 +144,11 @@ export const GroupTitle = (
value: groupData.value,
data: groupData.property.data$.value,
updateData: groupData.manager.updateData,
updateValue: value => groupData.manager.updateValue(groupData.rows, value),
updateValue: value =>
groupData.manager.updateValue(
groupData.rows.map(row => row.rowId),
value
),
readonly: ops.readonly,
};

View File

@@ -189,22 +189,25 @@ export const selectGroupByProperty = (
},
items: [
menu.group({
items: view.propertiesWithoutFilter$.value
.filter(id => {
if (view.propertyGet(id).type$.value === 'title') {
items: view.propertiesRaw$.value
.filter(property => {
if (property.type$.value === 'title') {
return false;
}
return !!groupByMatcher.match(view.propertyGet(id).dataType$.value);
const dataType = property.dataType$.value;
if (!dataType) {
return false;
}
return !!groupByMatcher.match(dataType);
})
.map<MenuConfig>(id => {
const property = view.propertyGet(id);
.map<MenuConfig>(property => {
return menu.action({
name: property.name$.value,
isSelected: group.property$.value?.id === id,
isSelected: group.property$.value?.id === property.id,
prefix: html` <uni-lit .uni="${property.icon}"></uni-lit>`,
select: () => {
group.changeGroup(id);
ops?.onSelect?.(id);
group.changeGroup(property.id);
ops?.onSelect?.(property.id);
},
});
}),
@@ -254,7 +257,7 @@ export const popGroupSetting = (
if (!type) {
return;
}
const icon = view.propertyIconGet(type);
const icon = groupProperty.icon;
const menuHandler = popMenu(target, {
options: {
title: {

View File

@@ -7,11 +7,12 @@ import { computed, type ReadonlySignal } from '@preact/signals-core';
import type { GroupBy, GroupProperty } from '../common/types.js';
import type { TypeInstance } from '../logical/type.js';
import { createTraitKey } from '../traits/key.js';
import { computedLock } from '../utils/lock.js';
import type { Property } from '../view-manager/property.js';
import type { Row } from '../view-manager/row.js';
import type { SingleView } from '../view-manager/single-view.js';
import { defaultGroupBy } from './default.js';
import { groupByMatcher } from './matcher.js';
export type GroupData = {
manager: GroupTrait;
property: Property;
@@ -19,12 +20,10 @@ export type GroupData = {
name: string;
type: TypeInstance;
value: unknown;
rows: string[];
rows: Row[];
};
export class GroupTrait {
private preDataList: GroupData[] | undefined;
config$ = computed(() => {
const groupBy = this.groupBy$.value;
if (!groupBy) {
@@ -42,7 +41,7 @@ export class GroupTrait {
if (!groupBy) {
return;
}
return this.view.propertyGet(groupBy.columnId);
return this.view.propertyGetOrCreate(groupBy.columnId);
});
staticGroupDataMap$ = computed<
@@ -81,8 +80,9 @@ export class GroupTrait {
const groupMap: Record<string, GroupData> = Object.fromEntries(
Object.entries(staticGroupMap).map(([k, v]) => [k, { ...v, rows: [] }])
);
this.view.rows$.value.forEach(id => {
const value = this.view.cellJsonValueGet(id, groupBy.columnId);
this.view.rows$.value.forEach(row => {
const value = this.view.cellGetOrCreate(row.rowId, groupBy.columnId)
.jsonValue$.value;
const keys = config.valuesGroup(value, tType);
keys.forEach(({ key, value }) => {
if (!groupMap[key]) {
@@ -96,40 +96,36 @@ export class GroupTrait {
type: tType,
};
}
groupMap[key].rows.push(id);
groupMap[key].rows.push(row);
});
});
return groupMap;
});
private readonly _groupsDataList$ = computed(() => {
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
}
const sortedGroup = this.ops.sortGroup(Object.keys(groupMap));
sortedGroup.forEach(key => {
if (!groupMap[key]) return;
groupMap[key].rows = this.ops.sortRow(key, groupMap[key].rows);
});
return (this.preDataList = sortedGroup
.map(key => groupMap[key])
.filter((v): v is GroupData => v != null));
});
groupsDataList$ = computed(() => {
if (this.view.isLocked$.value) {
return this.preDataList;
}
return (this.preDataList = this._groupsDataList$.value);
});
groupsDataList$ = computedLock(
computed(() => {
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
}
const sortedGroup = this.ops.sortGroup(Object.keys(groupMap));
sortedGroup.forEach(key => {
if (!groupMap[key]) return;
groupMap[key].rows = this.ops.sortRow(key, groupMap[key].rows);
});
return sortedGroup
.map(key => groupMap[key])
.filter((v): v is GroupData => v != null);
}),
this.view.isLocked$
);
updateData = (data: NonNullable<unknown>) => {
const propertyId = this.propertyId;
if (!propertyId) {
return;
}
this.view.propertyDataSet(propertyId, data);
this.view.propertyGetOrCreate(propertyId).dataUpdate(() => data);
};
get addGroup() {
@@ -137,7 +133,7 @@ export class GroupTrait {
if (!type) {
return;
}
return this.view.propertyMetaGet(type)?.config.addGroup;
return this.view.manager.dataSource.propertyMetaGet(type)?.config.addGroup;
}
get propertyId() {
@@ -150,7 +146,7 @@ export class GroupTrait {
private readonly ops: {
groupBySet: (groupBy: GroupBy | undefined) => void;
sortGroup: (keys: string[]) => string[];
sortRow: (groupKey: string, rowIds: string[]) => string[];
sortRow: (groupKey: string, rows: Row[]) => Row[];
changeGroupSort: (keys: string[]) => void;
changeRowSort: (
groupKeys: string[],
@@ -169,8 +165,11 @@ export class GroupTrait {
const addTo = this.config$.value?.addToGroup ?? (value => value);
const v = groupMap[key]?.value;
if (v != null) {
const newValue = addTo(v, this.view.cellJsonValueGet(rowId, propertyId));
this.view.cellJsonValueSet(rowId, propertyId, newValue);
const newValue = addTo(
v,
this.view.cellGetOrCreate(rowId, propertyId).jsonValue$.value
);
this.view.cellGetOrCreate(rowId, propertyId).valueSet(newValue);
}
}
@@ -191,8 +190,10 @@ export class GroupTrait {
this.ops.groupBySet(undefined);
return;
}
const column = this.view.propertyGet(columnId);
const propertyMeta = this.view.propertyMetaGet(column.type$.value);
const column = this.view.propertyGetOrCreate(columnId);
const propertyMeta = this.view.manager.dataSource.propertyMetaGet(
column.type$.value
);
if (propertyMeta) {
this.ops.groupBySet(
defaultGroupBy(
@@ -238,15 +239,18 @@ export class GroupTrait {
if (group) {
newValue = remove(
group.value,
this.view.cellJsonValueGet(rowId, propertyId)
this.view.cellGetOrCreate(rowId, propertyId).jsonValue$.value
);
}
const addTo = this.config$.value?.addToGroup ?? (value => value);
newValue = addTo(groupMap[toGroupKey]?.value ?? null, newValue);
this.view.cellJsonValueSet(rowId, propertyId, newValue);
this.view.cellGetOrCreate(rowId, propertyId).jsonValueSet(newValue);
}
const rows = groupMap[toGroupKey]?.rows.filter(id => id !== rowId) ?? [];
const index = insertPositionToIndex(position, rows, id => id);
const rows =
groupMap[toGroupKey]?.rows
.filter(row => row.rowId !== rowId)
.map(row => row.rowId) ?? [];
const index = insertPositionToIndex(position, rows, row => row);
rows.splice(index, 0, rowId);
this.changeCardSort(toGroupKey, rows);
}
@@ -278,9 +282,9 @@ export class GroupTrait {
const remove = this.config$.value?.removeFromGroup ?? (() => undefined);
const newValue = remove(
groupMap[key]?.value ?? null,
this.view.cellJsonValueGet(rowId, propertyId)
this.view.cellGetOrCreate(rowId, propertyId).jsonValue$.value
);
this.view.cellValueSet(rowId, propertyId, newValue);
this.view.cellGetOrCreate(rowId, propertyId).valueSet(newValue);
}
updateValue(rows: string[], value: unknown) {
@@ -288,8 +292,8 @@ export class GroupTrait {
if (!propertyId) {
return;
}
rows.forEach(id => {
this.view.cellJsonValueSet(id, propertyId, value);
rows.forEach(rowId => {
this.view.cellGetOrCreate(rowId, propertyId).jsonValueSet(value);
});
}
}

View File

@@ -76,7 +76,7 @@ export type PropertyConfig<Data, RawValue = unknown, JsonValue = unknown> = {
};
fixed?: {
defaultData: Data;
defaultOrder?: string;
defaultOrder?: 'start' | 'end';
defaultShow?: boolean;
};
minWidth?: number;

View File

@@ -4,7 +4,7 @@ import type { DataTypeOf } from '../logical/data-type.js';
import { t } from '../logical/index.js';
import type { TypeInstance } from '../logical/type.js';
import { typeSystem } from '../logical/type-system.js';
import type { SingleView } from '../view-manager/index.js';
import type { Row, SingleView } from '../view-manager/index.js';
import type { Sort } from './types.js';
export const Compare = {
@@ -16,14 +16,14 @@ const evalRef = (
view: SingleView,
ref: VariableRef
):
| ((row: string) => {
| ((row: Row) => {
value: unknown;
ttype?: TypeInstance;
})
| undefined => {
const ttype = view.propertyDataTypeGet(ref.name);
const ttype = view.propertyGetOrCreate(ref.name).dataType$.value;
return row => ({
value: view.cellJsonValueGet(row, ref.name),
value: view.cellGetOrCreate(row.rowId, ref.name).jsonValue$.value,
ttype,
});
};
@@ -153,7 +153,7 @@ const compare = (type: TypeInstance, a: unknown, b: unknown): CompareType => {
export const evalSort = (
sort: Sort,
view: SingleView
): ((rowA: string, rowB: string) => number) | undefined => {
): ((rowA: Row, rowB: Row) => number) | undefined => {
if (sort.sortBy.length) {
const sortBy = sort.sortBy.map(sort => {
return {

View File

@@ -1,7 +1,7 @@
import { computed, type ReadonlySignal } from '@preact/signals-core';
import { createTraitKey } from '../traits/key.js';
import type { SingleView } from '../view-manager/index.js';
import type { Row, SingleView } from '../view-manager/index.js';
import { evalSort } from './eval.js';
import type { Sort, SortBy } from './types.js';
@@ -16,7 +16,7 @@ export class SortManager {
});
};
sort = (rows: string[]) => {
sort = (rows: Row[]) => {
if (!this.sort$.value) {
return rows;
}

View File

@@ -0,0 +1,15 @@
import { computed, type ReadonlySignal } from '@preact/signals-core';
export const computedLock = <T>(
value$: ReadonlySignal<T>,
lock$: ReadonlySignal<boolean>
): ReadonlySignal<T> => {
let previousValue: T;
return computed(() => {
if (lock$.value) {
return previousValue ?? value$.value;
}
previousValue = value$.value;
return previousValue;
});
};

View File

@@ -1,5 +1,6 @@
import { computed, type ReadonlySignal } from '@preact/signals-core';
import { fromJson } from '../property/utils.js';
import type { Property } from './property.js';
import type { Row } from './row.js';
import type { SingleView } from './single-view.js';
@@ -9,18 +10,19 @@ export interface Cell<
JsonValue = unknown,
Data extends Record<string, unknown> = Record<string, unknown>,
> {
readonly rowId: string;
readonly view: SingleView;
readonly rowId: string;
readonly row: Row;
readonly propertyId: string;
readonly property: Property<RawValue, JsonValue, Data>;
readonly isEmpty$: ReadonlySignal<boolean>;
readonly stringValue$: ReadonlySignal<string>;
readonly jsonValue$: ReadonlySignal<JsonValue>;
readonly isEmpty$: ReadonlySignal<boolean>;
readonly value$: ReadonlySignal<RawValue | undefined>;
readonly jsonValue$: ReadonlySignal<JsonValue | undefined>;
readonly stringValue$: ReadonlySignal<string | undefined>;
valueSet(value: RawValue | undefined): void;
jsonValueSet(value: JsonValue | undefined): void;
}
export class CellBase<
@@ -29,10 +31,12 @@ export class CellBase<
Data extends Record<string, unknown> = Record<string, unknown>,
> implements Cell<RawValue, JsonValue, Data>
{
get dataSource() {
return this.view.manager.dataSource;
}
meta$ = computed(() => {
return this.view.manager.dataSource.propertyMetaGet(
this.property.type$.value
);
return this.dataSource.propertyMetaGet(this.property.type$.value);
});
value$ = computed(() => {
@@ -51,20 +55,37 @@ export class CellBase<
);
});
jsonValue$: ReadonlySignal<JsonValue> = computed(() => {
return this.view.cellJsonValueGet(this.rowId, this.propertyId) as JsonValue;
jsonValue$: ReadonlySignal<JsonValue | undefined> = computed(() => {
const toJson = this.property.meta$.value?.config.rawValue.toJson;
if (!toJson) {
return undefined;
}
return (
(toJson({
value: this.value$.value,
data: this.property.data$.value,
dataSource: this.dataSource,
}) as JsonValue) ?? undefined
);
});
property$ = computed(() => {
return this.view.propertyGet(this.propertyId) as Property<
return this.view.propertyGetOrCreate(this.propertyId) as Property<
RawValue,
JsonValue,
Data
>;
});
stringValue$: ReadonlySignal<string> = computed(() => {
return this.view.cellStringValueGet(this.rowId, this.propertyId)!;
stringValue$: ReadonlySignal<string | undefined> = computed(() => {
const toString = this.property.meta$.value?.config.rawValue.toString;
if (!toString) {
return;
}
return toString({
value: this.value$.value,
data: this.property.data$.value,
});
});
get property(): Property<RawValue, JsonValue, Data> {
@@ -72,7 +93,7 @@ export class CellBase<
}
get row(): Row {
return this.view.rowGet(this.rowId);
return this.view.rowGetOrCreate(this.rowId);
}
constructor(
@@ -88,4 +109,17 @@ export class CellBase<
value
);
}
jsonValueSet(value: JsonValue | undefined): void {
const meta = this.property.meta$.value;
if (!meta) {
return;
}
const rawValue = fromJson(meta.config, {
value: value,
data: this.property.data$.value,
dataSource: this.view.manager.dataSource,
});
this.dataSource.cellValueChange(this.rowId, this.propertyId, rawValue);
}
}

View File

@@ -1,8 +1,9 @@
import type { UniComponent } from '@blocksuite/affine-shared/types';
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
import { computed, type ReadonlySignal } from '@preact/signals-core';
import type { TypeInstance } from '../logical/type.js';
import type { CellRenderer } from '../property/index.js';
import type { CellRenderer, PropertyMetaConfig } from '../property/index.js';
import type { PropertyDataUpdater } from '../types.js';
import type { Cell } from './cell.js';
import type { SingleView } from './single-view.js';
@@ -13,14 +14,17 @@ export interface Property<
Data extends Record<string, unknown> = Record<string, unknown>,
> {
readonly id: string;
readonly index: number;
readonly index$: ReadonlySignal<number | undefined>;
readonly view: SingleView;
readonly isFirst: boolean;
readonly isLast: boolean;
readonly isFirst$: ReadonlySignal<boolean>;
readonly isLast$: ReadonlySignal<boolean>;
readonly next$: ReadonlySignal<Property | undefined>;
readonly prev$: ReadonlySignal<Property | undefined>;
readonly readonly$: ReadonlySignal<boolean>;
readonly renderer$: ReadonlySignal<CellRenderer | undefined>;
readonly cells$: ReadonlySignal<Cell[]>;
readonly dataType$: ReadonlySignal<TypeInstance>;
readonly dataType$: ReadonlySignal<TypeInstance | undefined>;
readonly meta$: ReadonlySignal<PropertyMetaConfig | undefined>;
readonly icon?: UniComponent;
readonly delete?: () => void;
@@ -29,7 +33,7 @@ export interface Property<
readonly duplicate?: () => void;
get canDuplicate(): boolean;
cellGet(rowId: string): Cell<RawValue, JsonValue, Data>;
cellGetOrCreate(rowId: string): Cell<RawValue, JsonValue, Data>;
readonly data$: ReadonlySignal<Data>;
dataUpdate(updater: PropertyDataUpdater<Data>): void;
@@ -48,8 +52,16 @@ export interface Property<
valueGet(rowId: string): RawValue | undefined;
valueSet(rowId: string, value: RawValue | undefined): void;
stringValueGet(rowId: string): string;
stringValueGet(rowId: string): string | undefined;
valueSetFromString(rowId: string, value: string): void;
parseValueFromString(value: string):
| {
value: unknown;
data?: Record<string, unknown>;
}
| undefined;
move(position: InsertToPosition): void;
}
export abstract class PropertyBase<
@@ -58,125 +70,195 @@ export abstract class PropertyBase<
Data extends Record<string, unknown> = Record<string, unknown>,
> implements Property<RawValue, JsonValue, Data>
{
meta$ = computed(() => {
return this.dataSource.propertyMetaGet(this.type$.value);
});
cells$ = computed(() => {
return this.view.rows$.value.map(id => this.cellGet(id));
return this.view.rows$.value.map(row =>
this.view.cellGetOrCreate(row.rowId, this.id)
);
});
data$ = computed(() => {
return this.view.propertyDataGet(this.id) as Data;
return this.dataSource.propertyDataGet(this.id) as Data;
});
dataType$ = computed(() => {
return this.view.propertyDataTypeGet(this.id)!;
const type = this.type$.value;
if (!type) {
return;
}
const meta = this.dataSource.propertyMetaGet(type);
if (!meta) {
return;
}
return meta.config.jsonValue.type({
data: this.data$.value,
dataSource: this.dataSource,
});
});
hide$ = computed(() => {
return this.view.propertyHideGet(this.id);
});
abstract hide$: ReadonlySignal<boolean>;
name$ = computed(() => {
return this.view.propertyNameGet(this.id);
return this.dataSource.propertyNameGet(this.id);
});
readonly$ = computed(() => {
return this.view.readonly$.value || this.view.propertyReadonlyGet(this.id);
return (
this.view.readonly$.value || this.dataSource.propertyReadonlyGet(this.id)
);
});
type$ = computed(() => {
return this.view.propertyTypeGet(this.id)!;
return this.dataSource.propertyTypeGet(this.id)!;
});
renderer$ = computed(() => {
return this.view.propertyMetaGet(this.type$.value)?.renderer.cellRenderer;
return this.meta$.value?.renderer.cellRenderer;
});
get delete(): (() => void) | undefined {
return () => this.view.propertyDelete(this.id);
return () => this.dataSource.propertyDelete(this.id);
}
get duplicate(): (() => void) | undefined {
return () => this.view.propertyDuplicate(this.id);
return () => {
const id = this.dataSource.propertyDuplicate(this.id);
if (!id) {
return;
}
const property = this.view.propertyGetOrCreate(id);
property.move({
before: false,
id: this.id,
});
};
}
abstract move(position: InsertToPosition): void;
get icon(): UniComponent | undefined {
if (!this.type$.value) return undefined;
return this.view.propertyIconGet(this.type$.value);
return this.dataSource.propertyMetaGet(this.type$.value)?.renderer.icon;
}
get id(): string {
return this.propertyId;
}
get index(): number {
return this.view.propertyIndexGet(this.id);
}
index$ = computed(() => {
const index = this.view.propertyIds$.value.indexOf(this.id);
return index >= 0 ? index : undefined;
});
get isFirst(): boolean {
return this.view.propertyIndexGet(this.id) === 0;
}
isFirst$ = computed(() => {
return this.index$.value === 0;
});
get isLast(): boolean {
return (
this.view.propertyIndexGet(this.id) ===
this.view.properties$.value.length - 1
);
}
isLast$ = computed(() => {
return this.index$.value === this.view.propertyIds$.value.length - 1;
});
next$ = computed(() => {
const properties = this.view.properties$.value;
if (this.index$.value == null) {
return;
}
return properties[this.index$.value + 1];
});
prev$ = computed(() => {
const properties = this.view.properties$.value;
if (this.index$.value == null) {
return;
}
return properties[this.index$.value - 1];
});
get typeSet(): undefined | ((type: string) => void) {
return type => this.view.propertyTypeSet(this.id, type);
return type => this.dataSource.propertyTypeSet(this.id, type);
}
constructor(
public view: SingleView,
public propertyId: string
) {}
protected get dataSource() {
return this.view.manager.dataSource;
}
get canDelete(): boolean {
return this.view.propertyCanDelete(this.id);
return this.dataSource.propertyCanDelete(this.id);
}
get canDuplicate(): boolean {
return this.view.propertyCanDuplicate(this.id);
return this.dataSource.propertyCanDuplicate(this.id);
}
get typeCanSet(): boolean {
return this.view.propertyTypeCanSet(this.id);
return this.dataSource.propertyTypeCanSet(this.id);
}
get hideCanSet(): boolean {
return this.view.propertyCanHide(this.id);
return this.type$.value !== 'title';
}
cellGet(rowId: string): Cell<RawValue, JsonValue, Data> {
return this.view.cellGet(rowId, this.id) as Cell<RawValue, JsonValue, Data>;
cellGetOrCreate(rowId: string): Cell<RawValue, JsonValue, Data> {
return this.view.cellGetOrCreate(rowId, this.id) as Cell<
RawValue,
JsonValue,
Data
>;
}
dataUpdate(updater: PropertyDataUpdater<Data>): void {
const data = this.data$.value;
this.view.propertyDataSet(this.id, {
this.dataSource.propertyDataSet(this.id, {
...data,
...updater(data),
});
}
hideSet(hide: boolean): void {
this.view.propertyHideSet(this.id, hide);
}
abstract hideSet(hide: boolean): void;
nameSet(name: string): void {
this.view.propertyNameSet(this.id, name);
this.dataSource.propertyNameSet(this.id, name);
}
stringValueGet(rowId: string): string {
return this.cellGet(rowId).stringValue$.value;
stringValueGet(rowId: string): string | undefined {
return this.cellGetOrCreate(rowId).stringValue$.value;
}
valueGet(rowId: string): RawValue | undefined {
return this.cellGet(rowId).value$.value;
return this.cellGetOrCreate(rowId).value$.value;
}
valueSet(rowId: string, value: RawValue | undefined): void {
return this.cellGet(rowId).valueSet(value);
return this.cellGetOrCreate(rowId).valueSet(value);
}
parseValueFromString(value: string):
| {
value: unknown;
data?: Record<string, unknown>;
}
| undefined {
const type = this.type$.value;
if (!type) {
return;
}
const fromString =
this.dataSource.propertyMetaGet(type)?.config.rawValue.fromString;
if (!fromString) {
return;
}
return fromString({
value,
data: this.data$.value,
dataSource: this.dataSource,
});
}
valueSetFromString(rowId: string, value: string): void {
const data = this.view.propertyParseValueFromString(this.id, value);
const data = this.parseValueFromString(value);
if (!data) {
return;
}

View File

@@ -1,3 +1,4 @@
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
import { computed, type ReadonlySignal } from '@preact/signals-core';
import { type Cell, CellBase } from './cell.js';
@@ -6,17 +7,59 @@ import type { SingleView } from './single-view.js';
export interface Row {
readonly cells$: ReadonlySignal<Cell[]>;
readonly rowId: string;
index$: ReadonlySignal<number | undefined>;
prev$: ReadonlySignal<Row | undefined>;
next$: ReadonlySignal<Row | undefined>;
delete(): void;
move(position: InsertToPosition): void;
}
export class RowBase implements Row {
cells$ = computed(() => {
return this.singleView.propertyIds$.value.map(propertyId => {
return new CellBase(this.singleView, propertyId, this.rowId);
return this.singleView.propertiesRaw$.value.map(property => {
return new CellBase(this.singleView, property.id, this.rowId);
});
});
index$ = computed(() => {
const idx = this.singleView.rowIds$.value.indexOf(this.rowId);
return idx >= 0 ? idx : undefined;
});
prev$ = computed(() => {
const index = this.index$.value;
if (index == null) {
return;
}
return this.singleView.rows$.value[index - 1];
});
next$ = computed(() => {
const index = this.index$.value;
if (index == null) {
return;
}
return this.singleView.rows$.value[index + 1];
});
constructor(
readonly singleView: SingleView,
readonly rowId: string
) {}
get dataSource() {
return this.singleView.manager.dataSource;
}
delete(): void {
this.dataSource.rowDelete([this.rowId]);
}
move(position: InsertToPosition): void {
this.dataSource.rowMove(this.rowId, position);
}
}

View File

@@ -1,14 +1,12 @@
import type { UniComponent } from '@blocksuite/affine-shared/types';
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
import type { DataViewContextKey } from '../data-source/context.js';
import type { Variable } from '../expression/types.js';
import type { TypeInstance } from '../logical/type.js';
import type { PropertyMetaConfig } from '../property/property-config.js';
import { fromJson } from '../property/utils';
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';
@@ -36,49 +34,25 @@ export interface SingleView {
nameSet(name: string): void;
readonly propertyIds$: ReadonlySignal<string[]>;
readonly propertiesWithoutFilter$: ReadonlySignal<string[]>;
readonly propertiesRaw$: ReadonlySignal<Property[]>;
readonly propertyMap$: ReadonlySignal<Record<string, Property>>;
readonly properties$: ReadonlySignal<Property[]>;
readonly detailProperties$: ReadonlySignal<string[]>;
readonly rows$: ReadonlySignal<string[]>;
readonly propertyIds$: ReadonlySignal<string[]>;
readonly detailProperties$: ReadonlySignal<Property[]>;
readonly rowsRaw$: ReadonlySignal<Row[]>;
readonly rows$: ReadonlySignal<Row[]>;
readonly rowIds$: ReadonlySignal<string[]>;
readonly vars$: ReadonlySignal<Variable[]>;
readonly featureFlags$: ReadonlySignal<DatabaseFlags>;
cellValueGet(rowId: string, propertyId: string): unknown;
cellValueSet(rowId: string, propertyId: string, value: unknown): void;
cellJsonValueGet(rowId: string, propertyId: string): unknown | null;
cellJsonValueSet(rowId: string, propertyId: string, value: unknown): void;
cellStringValueGet(rowId: string, propertyId: string): string | undefined;
cellGet(rowId: string, propertyId: string): Cell;
propertyParseValueFromString(
propertyId: string,
value: string
):
| {
value: unknown;
data?: Record<string, unknown>;
}
| undefined;
propertyGetOrCreate(propertyId: string): Property;
rowGetOrCreate(rowId: string): Row;
cellGetOrCreate(rowId: string, propertyId: string): Cell;
rowAdd(insertPosition: InsertToPosition): string;
rowDelete(ids: string[]): void;
rowMove(rowId: string, position: InsertToPosition): void;
rowGet(rowId: string): Row;
rowPrevGet(rowId: string): string | undefined;
rowNextGet(rowId: string): string | undefined;
rowsDelete(rows: string[]): void;
readonly propertyMetas$: ReadonlySignal<PropertyMetaConfig[]>;
@@ -87,54 +61,6 @@ export interface SingleView {
type?: string
): string | undefined;
propertyDelete(propertyId: string): void;
propertyCanDelete(propertyId: string): boolean;
propertyDuplicate(propertyId: string): void;
propertyCanDuplicate(propertyId: string): boolean;
propertyGet(propertyId: string): Property;
propertyMetaGet(type: string): PropertyMetaConfig | undefined;
propertyPreGet(propertyId: string): Property | undefined;
propertyNextGet(propertyId: string): Property | undefined;
propertyNameGet(propertyId: string): string;
propertyNameSet(propertyId: string, name: string): void;
propertyTypeGet(propertyId: string): string | undefined;
propertyTypeSet(propertyId: string, type: string): void;
propertyTypeCanSet(propertyId: string): boolean;
propertyHideGet(propertyId: string): boolean;
propertyHideSet(propertyId: string, hide: boolean): void;
propertyCanHide(propertyId: string): boolean;
propertyDataGet(propertyId: string): Record<string, unknown>;
propertyDataSet(propertyId: string, data: Record<string, unknown>): void;
propertyDataTypeGet(propertyId: string): TypeInstance | undefined;
propertyIndexGet(propertyId: string): number;
propertyIdGetByIndex(index: number): string | undefined;
propertyReadonlyGet(propertyId: string): boolean;
propertyMove(propertyId: string, position: InsertToPosition): void;
propertyIconGet(type: string): UniComponent | undefined;
contextGet<T>(key: DataViewContextKey<T>): T;
traitGet<T>(key: TraitKey<T>): T | undefined;
@@ -158,7 +84,7 @@ export abstract class SingleViewBase<
return this.dataSource.viewDataGet(this.id) as ViewData | undefined;
});
abstract detailProperties$: ReadonlySignal<string[]>;
abstract detailProperties$: ReadonlySignal<Property[]>;
protected lockRows$ = signal(false);
@@ -172,43 +98,56 @@ export abstract class SingleViewBase<
return this.data$.value?.name ?? '';
});
preRows: string[] = [];
abstract propertyIds$: ReadonlySignal<string[]>;
properties$ = computed(() => {
return this.propertyIds$.value.map(
id => this.propertyGet(id) as ReturnType<this['propertyGet']>
);
propertyIds$: ReadonlySignal<string[]> = computed(() => {
return this.properties$.value.map(v => v.id);
});
abstract propertiesWithoutFilter$: ReadonlySignal<string[]>;
propertyMap$: ReadonlySignal<Record<string, Property>> = computed(() => {
return Object.fromEntries(this.properties$.value.map(v => [v.id, v]));
});
abstract properties$: ReadonlySignal<Property[]>;
abstract propertiesRaw$: ReadonlySignal<Property[]>;
abstract readonly$: ReadonlySignal<boolean>;
rows$ = computed(() => {
if (this.lockRows$.value) {
return this.preRows;
}
return (this.preRows = this.rowsMapping(this.dataSource.rows$.value));
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.dataSource.rowDelete(rows);
}
rowIds$ = computed(() => {
return this.rowsRaw$.value.map(v => v.rowId);
});
vars$ = computed(() => {
return this.propertiesWithoutFilter$.value.flatMap(id => {
const v = this.propertyGet(id);
const propertyMeta = this.dataSource.propertyMetaGet(v.type$.value);
return this.propertiesRaw$.value.flatMap(property => {
const propertyMeta = this.dataSource.propertyMetaGet(
property.type$.value
);
if (!propertyMeta) {
return [];
}
return {
id: v.id,
name: v.name$.value,
id: property.id,
name: property.name$.value,
type: propertyMeta.config.jsonValue.type({
data: v.data$.value,
data: property.data$.value,
dataSource: this.dataSource,
}),
icon: v.icon,
propertyType: v.type$.value,
icon: property.icon,
propertyType: property.type$.value,
};
});
});
@@ -240,29 +179,13 @@ export abstract class SingleViewBase<
public id: string
) {}
propertyCanDelete(propertyId: string): boolean {
return this.dataSource.propertyCanDelete(propertyId);
}
propertyCanDuplicate(propertyId: string): boolean {
return this.dataSource.propertyCanDuplicate(propertyId);
}
propertyTypeCanSet(propertyId: string): boolean {
return this.dataSource.propertyTypeCanSet(propertyId);
}
propertyCanHide(propertyId: string): boolean {
return this.propertyTypeGet(propertyId) !== 'title';
}
private searchRowsMapping(rows: string[], searchString: string): string[] {
return rows.filter(id => {
private searchRowsMapping(rows: Row[], searchString: string): Row[] {
return rows.filter(row => {
if (searchString) {
const containsSearchString = this.propertyIds$.value.some(
propertyId => {
return this.cellStringValueGet(id, propertyId)
?.toLowerCase()
return this.cellGetOrCreate(row.rowId, propertyId)
.stringValue$.value?.toLowerCase()
.includes(searchString?.toLowerCase());
}
);
@@ -270,66 +193,14 @@ export abstract class SingleViewBase<
return false;
}
}
return this.isShow(id);
return this.isShow(row.rowId);
});
}
cellGet(rowId: string, propertyId: string): Cell {
cellGetOrCreate(rowId: string, propertyId: string): Cell {
return new CellBase(this, propertyId, rowId);
}
cellJsonValueGet(rowId: string, propertyId: string): unknown | null {
const type = this.propertyTypeGet(propertyId);
if (!type) {
return null;
}
return (
this.dataSource.propertyMetaGet(type)?.config.rawValue.toJson({
value: this.dataSource.cellValueGet(rowId, propertyId),
data: this.propertyDataGet(propertyId),
dataSource: this.dataSource,
}) ?? null
);
}
cellJsonValueSet(rowId: string, propertyId: string, value: unknown): void {
const type = this.propertyTypeGet(propertyId);
if (!type) {
return;
}
const config = this.dataSource.propertyMetaGet(type)?.config;
if (!config) {
return;
}
const rawValue = fromJson(config, {
value: value,
data: this.propertyDataGet(propertyId),
dataSource: this.dataSource,
});
this.dataSource.cellValueChange(rowId, propertyId, rawValue);
}
cellStringValueGet(rowId: string, propertyId: string): string | undefined {
const type = this.propertyTypeGet(propertyId);
if (!type) {
return;
}
return (
this.dataSource.propertyMetaGet(type)?.config.rawValue.toString({
value: this.dataSource.cellValueGet(rowId, propertyId),
data: this.propertyDataGet(propertyId),
}) ?? ''
);
}
cellValueGet(rowId: string, propertyId: string): unknown {
return this.dataSource.cellValueGet(rowId, propertyId);
}
cellValueSet(rowId: string, propertyId: string, value: unknown): void {
this.dataSource.cellValueChange(rowId, propertyId, value);
}
contextGet<T>(key: DataViewContextKey<T>): T {
return this.dataSource.contextGet(key);
}
@@ -365,144 +236,22 @@ export abstract class SingleViewBase<
if (!id) {
return;
}
this.propertyMove(id, position);
const property = this.propertyGetOrCreate(id);
property.move(position);
return id;
}
propertyDataGet(propertyId: string): Record<string, unknown> {
return this.dataSource.propertyDataGet(propertyId);
}
propertyDataSet(propertyId: string, data: Record<string, unknown>): void {
this.dataSource.propertyDataSet(propertyId, data);
}
propertyDataTypeGet(propertyId: string): TypeInstance | undefined {
const type = this.propertyTypeGet(propertyId);
if (!type) {
return;
}
const meta = this.dataSource.propertyMetaGet(type);
if (!meta) {
return;
}
return meta.config.jsonValue.type({
data: this.propertyDataGet(propertyId),
dataSource: this.dataSource,
});
}
propertyDelete(propertyId: string): void {
this.dataSource.propertyDelete(propertyId);
}
propertyDuplicate(propertyId: string): void {
const id = this.dataSource.propertyDuplicate(propertyId);
if (!id) {
return;
}
this.propertyMove(id, {
before: false,
id: propertyId,
});
}
abstract propertyGet(propertyId: string): Property;
abstract propertyHideGet(propertyId: string): boolean;
abstract propertyHideSet(propertyId: string, hide: boolean): void;
propertyIconGet(type: string): UniComponent | undefined {
return this.dataSource.propertyMetaGet(type)?.renderer.icon;
}
propertyIdGetByIndex(index: number): string | undefined {
return this.propertyIds$.value[index];
}
propertyIndexGet(propertyId: string): number {
return this.propertyIds$.value.indexOf(propertyId);
}
propertyMetaGet(type: string): PropertyMetaConfig | undefined {
return this.dataSource.propertyMetaGet(type);
}
abstract propertyMove(propertyId: string, position: InsertToPosition): void;
propertyNameGet(propertyId: string): string {
return this.dataSource.propertyNameGet(propertyId);
}
propertyNameSet(propertyId: string, name: string): void {
this.dataSource.propertyNameSet(propertyId, name);
}
propertyNextGet(propertyId: string): Property | undefined {
const index = this.propertyIndexGet(propertyId);
const nextId = this.propertyIdGetByIndex(index + 1);
if (!nextId) return;
return this.propertyGet(nextId);
}
propertyParseValueFromString(propertyId: string, cellData: string) {
const type = this.propertyTypeGet(propertyId);
if (!type) {
return;
}
const fromString =
this.dataSource.propertyMetaGet(type)?.config.rawValue.fromString;
if (!fromString) {
return;
}
return fromString({
value: cellData,
data: this.propertyDataGet(propertyId),
dataSource: this.dataSource,
});
}
propertyPreGet(propertyId: string): Property | undefined {
const index = this.propertyIndexGet(propertyId);
const prevId = this.propertyIdGetByIndex(index - 1);
if (!prevId) return;
return this.propertyGet(prevId);
}
propertyReadonlyGet(propertyId: string): boolean {
return this.dataSource.propertyReadonlyGet(propertyId);
}
propertyTypeGet(propertyId: string): string | undefined {
return this.dataSource.propertyTypeGet(propertyId);
}
propertyTypeSet(propertyId: string, type: string): void {
this.dataSource.propertyTypeSet(propertyId, type);
}
abstract propertyGetOrCreate(propertyId: string): Property;
rowAdd(insertPosition: InsertToPosition | number): string {
return this.dataSource.rowAdd(insertPosition);
}
rowDelete(ids: string[]): void {
this.dataSource.rowDelete(ids);
}
rowGet(rowId: string): Row {
rowGetOrCreate(rowId: string): Row {
return new RowBase(this, rowId);
}
rowMove(rowId: string, position: InsertToPosition): void {
this.dataSource.rowMove(rowId, position);
}
abstract rowNextGet(rowId: string): string | undefined;
abstract rowPrevGet(rowId: string): string | undefined;
protected rowsMapping(rows: string[]): string[] {
protected rowsMapping(rows: Row[]): Row[] {
return this.searchRowsMapping(rows, this.searchString.value);
}

View File

@@ -71,7 +71,6 @@ export const kanbanViewModel = kanbanViewType.createModel<KanbanViewData>({
return {
columns: columns.map(id => ({
id: id,
hide: false,
})),
filter: {
type: 'group',

View File

@@ -2,7 +2,7 @@ import {
insertPositionToIndex,
type InsertToPosition,
} from '@blocksuite/affine-shared/utils';
import { computed, type ReadonlySignal } from '@preact/signals-core';
import { computed } from '@preact/signals-core';
import { evalFilter } from '../../core/filter/eval.js';
import { generateDefaultValues } from '../../core/filter/generate-default-values.js';
@@ -20,7 +20,7 @@ import { SingleViewBase } from '../../core/view-manager/single-view.js';
import type { KanbanViewData } from './define.js';
export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
propertiesWithoutFilter$ = computed(() => {
propertiesRaw$ = computed(() => {
const needShow = new Set(this.dataSource.properties$.value);
const result: string[] = [];
this.data$.value?.columns.forEach(v => {
@@ -30,12 +30,16 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
}
});
result.push(...needShow);
return result;
return result.map(id => this.propertyGetOrCreate(id));
});
properties$ = computed(() => {
return this.propertiesRaw$.value.filter(property => !property.hide$.value);
});
detailProperties$ = computed(() => {
return this.propertiesWithoutFilter$.value.filter(
id => this.propertyTypeGet(id) !== 'title'
return this.propertiesRaw$.value.filter(
property => property.type$.value !== 'title'
);
});
@@ -76,9 +80,13 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
v => v,
this.view?.groupProperties.map(v => v.key) ?? []
),
sortRow: (key, ids) => {
sortRow: (key, rows) => {
const property = this.view?.groupProperties.find(v => v.key === key);
return sortByManually(ids, v => v, property?.manuallyCardSort ?? []);
return sortByManually(
rows,
v => v.rowId,
property?.manuallyCardSort ?? []
);
},
changeGroupSort: keys => {
const map = new Map(this.view?.groupProperties.map(v => [v.key, v]));
@@ -134,28 +142,20 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
mainProperties$ = computed(() => {
return (
this.data$.value?.header ?? {
titleColumn: this.propertiesWithoutFilter$.value.find(
id => this.propertyTypeGet(id) === 'title'
),
titleColumn: this.propertiesRaw$.value.find(
property => property.type$.value === 'title'
)?.id,
iconColumn: 'type',
}
);
});
propertyIds$: ReadonlySignal<string[]> = computed(() => {
return this.propertiesWithoutFilter$.value.filter(
id => !this.propertyHideGet(id)
);
});
readonly$ = computed(() => {
return this.manager.readonly$.value;
});
get columns(): string[] {
return this.propertiesWithoutFilter$.value.filter(
id => !this.propertyHideGet(id)
);
get columns(): KanbanColumn[] {
return this.propertiesRaw$.value.filter(property => !property.hide$.value);
}
get filter(): FilterGroup {
@@ -182,8 +182,8 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
if (filter.conditions.length > 0) {
const defaultValues = generateDefaultValues(filter, this.vars$.value);
Object.entries(defaultValues).forEach(([propertyId, jsonValue]) => {
const property = this.propertyGet(propertyId);
const propertyMeta = this.propertyMetaGet(property.type$.value);
const property = this.propertyGetOrCreate(propertyId);
const propertyMeta = property.meta$.value;
if (!propertyMeta) {
return;
}
@@ -192,7 +192,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
data: property.data$.value,
dataSource: this.dataSource,
});
this.cellValueSet(id, propertyId, value);
this.cellGetOrCreate(id, propertyId).valueSet(value);
});
}
@@ -204,7 +204,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
if (!columnId) {
return;
}
return this.propertyGet(columnId);
return this.propertyGetOrCreate(columnId);
}
getHeaderIcon(_rowId: string): KanbanColumn | undefined {
@@ -212,7 +212,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
if (!columnId) {
return;
}
return this.propertyGet(columnId);
return this.propertyGetOrCreate(columnId);
}
getHeaderTitle(_rowId: string): KanbanColumn | undefined {
@@ -220,7 +220,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
if (!columnId) {
return;
}
return this.propertyGet(columnId);
return this.propertyGetOrCreate(columnId);
}
hasHeader(_rowId: string): boolean {
@@ -248,7 +248,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
const rowMap = Object.fromEntries(
this.properties$.value.map(column => [
column.id,
column.cellGet(rowId).jsonValue$.value,
column.cellGetOrCreate(rowId).jsonValue$.value,
])
);
return evalFilter(this.filter$.value, rowMap);
@@ -256,32 +256,16 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
return true;
}
propertyGet(columnId: string): KanbanColumn {
propertyGetOrCreate(columnId: string): KanbanColumn {
return new KanbanColumn(this, columnId);
}
}
propertyHideGet(columnId: string): boolean {
return this.view?.columns.find(v => v.id === columnId)?.hide ?? false;
}
propertyHideSet(columnId: string, hide: boolean): void {
this.dataUpdate(view => {
return {
columns: view.columns.map(v =>
v.id === columnId
? {
...v,
hide,
}
: v
),
};
});
}
propertyMove(columnId: string, toAfterOfColumn: InsertToPosition): void {
this.dataUpdate(view => {
const columnIndex = view.columns.findIndex(v => v.id === columnId);
type KanbanColumnData = KanbanViewData['columns'][number];
export class KanbanColumn extends PropertyBase {
override move(position: InsertToPosition): void {
this.kanbanView.dataUpdate(view => {
const columnIndex = view.columns.findIndex(v => v.id === this.id);
if (columnIndex < 0) {
return {};
}
@@ -290,7 +274,7 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
if (!column) {
return {};
}
const index = insertPositionToIndex(toAfterOfColumn, columns);
const index = insertPositionToIndex(position, columns);
columns.splice(index, 0, column);
return {
columns,
@@ -298,23 +282,47 @@ export class KanbanSingleView extends SingleViewBase<KanbanViewData> {
});
}
override rowMove(rowId: string, position: InsertToPosition): void {
this.dataSource.rowMove(rowId, position);
override hideSet(hide: boolean): void {
this.viewDataUpdate(data => {
return {
...data,
hide,
};
});
}
hide$ = computed(() => {
const hideFromViewData = this.viewData$.value?.hide;
if (hideFromViewData != null) {
return hideFromViewData;
}
const defaultShow = this.meta$.value?.config.fixed?.defaultShow;
if (defaultShow != null) {
return !defaultShow;
}
return false;
});
viewData$ = computed(() => {
return this.kanbanView.data$.value?.columns.find(v => v.id === this.id);
});
viewDataUpdate(
updater: (viewData: KanbanColumnData) => Partial<KanbanColumnData>
): void {
this.kanbanView.dataUpdate(data => {
return {
...data,
columns: data.columns.map(v =>
v.id === this.id ? { ...v, ...updater(v) } : v
),
};
});
}
override rowNextGet(rowId: string): string | undefined {
const index = this.rows$.value.indexOf(rowId);
return this.rows$.value[index + 1];
}
override rowPrevGet(rowId: string): string | undefined {
const index = this.rows$.value.indexOf(rowId);
return this.rows$.value[index - 1];
}
}
export class KanbanColumn extends PropertyBase {
constructor(dataViewManager: KanbanSingleView, columnId: string) {
super(dataViewManager, columnId);
constructor(
private readonly kanbanView: KanbanSingleView,
columnId: string
) {
super(kanbanView, columnId);
}
}

View File

@@ -155,7 +155,7 @@ export class MobileKanbanCard extends SignalWatcher(
return;
}
return html` <div class="mobile-card-header-icon">
${icon.cellGet(this.cardId).value$.value}
${icon.cellGetOrCreate(this.cardId).value$.value}
</div>`;
}

View File

@@ -130,7 +130,7 @@ export class MobileKanbanCell extends SignalWatcher(
override render() {
const props: CellRenderProps = {
cell: this.column.cellGet(this.cardId),
cell: this.column.cellGetOrCreate(this.cardId),
isEditing$: this.isEditing$,
selectCurrentCell: this.selectCurrentCell,
};

View File

@@ -77,15 +77,15 @@ export class MobileKanbanGroup extends SignalWatcher(
name: 'Ungroup',
hide: () => this.group.value == null,
select: () => {
this.group.rows.forEach(id => {
this.group.manager.removeFromGroup(id, this.group.key);
this.group.rows.forEach(row => {
this.group.manager.removeFromGroup(row.rowId, this.group.key);
});
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowDelete(this.group.rows);
this.view.rowsDelete(this.group.rows.map(row => row.rowId));
},
}),
],
@@ -106,15 +106,15 @@ export class MobileKanbanGroup extends SignalWatcher(
<div class="mobile-group-body">
${repeat(
cards,
id => id,
id => {
row => row.rowId,
row => {
return html`
<mobile-kanban-card
data-card-id="${id}"
data-card-id="${row.rowId}"
.groupKey="${this.group.key}"
.dataViewEle="${this.dataViewEle}"
.view="${this.view}"
.cardId="${id}"
.cardId="${row.rowId}"
></mobile-kanban-card>
`;
}

View File

@@ -106,7 +106,7 @@ export const popCardMenu = (
},
prefix: DeleteIcon(),
select: () => {
view.rowDelete([cardId]);
view.rowsDelete([cardId]);
},
}),
],

View File

@@ -230,7 +230,7 @@ export class KanbanCard extends SignalWatcher(
return;
}
return html` <div class="card-header-icon">
${icon.cellGet(this.cardId).value$.value}
${icon.cellGetOrCreate(this.cardId).value$.value}
</div>`;
}

View File

@@ -129,7 +129,7 @@ export class KanbanCell extends SignalWatcher(
override render() {
const props: CellRenderProps = {
cell: this.column.cellGet(this.cardId),
cell: this.column.cellGetOrCreate(this.cardId),
isEditing$: this.isEditing$,
selectCurrentCell: this.selectCurrentCell,
};

View File

@@ -116,7 +116,7 @@ export class KanbanSelectionController implements ReactiveController {
return;
}
if (selection.selectionType === 'card') {
this.host.props.view.rowDelete(selection.cards.map(v => v.cardId));
this.host.props.view.rowsDelete(selection.cards.map(v => v.cardId));
this.selection = undefined;
}
}
@@ -155,12 +155,13 @@ export class KanbanSelectionController implements ReactiveController {
focusFirstCell() {
const group = this.host.groupManager?.groupsDataList$.value?.[0];
const card = group?.rows[0];
const columnId = card && this.host.props.view.getHeaderTitle(card)?.id;
const columnId =
card && this.host.props.view.getHeaderTitle(card.rowId)?.id;
if (group && card && columnId) {
this.selection = {
selectionType: 'cell',
groupKey: group.key,
cardId: card,
cardId: card.rowId,
columnId,
isEditing: false,
};

View File

@@ -143,15 +143,15 @@ export class KanbanGroup extends SignalWatcher(
name: 'Ungroup',
hide: () => this.group.value == null,
select: () => {
this.group.rows.forEach(id => {
this.group.manager.removeFromGroup(id, this.group.key);
this.group.rows.forEach(row => {
this.group.manager.removeFromGroup(row.rowId, this.group.key);
});
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowDelete(this.group.rows);
this.view.rowsDelete(this.group.rows.map(row => row.rowId));
},
}),
]);
@@ -170,15 +170,15 @@ export class KanbanGroup extends SignalWatcher(
<div class="group-body">
${repeat(
cards,
id => id,
id => {
row => row.rowId,
row => {
return html`
<affine-data-view-kanban-card
data-card-id="${id}"
data-card-id="${row.rowId}"
.groupKey="${this.group.key}"
.dataViewEle="${this.dataViewEle}"
.view="${this.view}"
.cardId="${id}"
.cardId="${row.rowId}"
></affine-data-view-kanban-card>
`;
}

View File

@@ -11,7 +11,7 @@ import {
type SingleView,
} from '../../../core/index.js';
import { TableViewAreaSelection } from '../selection';
import type { TableColumn } from '../table-view-manager.js';
import type { TableProperty } from '../table-view-manager.js';
export class MobileTableCell extends SignalWatcher(
WithDisposable(ShadowlessElement)
@@ -38,13 +38,13 @@ export class MobileTableCell extends SignalWatcher(
private readonly _cell = signal<DataViewCellLifeCycle>();
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor rowId!: string;
cell$ = computed(() => {
return this.column.cellGet(this.rowId);
return this.column.cellGetOrCreate(this.rowId);
});
isSelectionEditing$ = computed(() => {

View File

@@ -25,7 +25,7 @@ import { inputConfig, typeConfig } from '../../../core/common/property-menu.js';
import type { Property } from '../../../core/view-manager/property.js';
import { numberFormats } from '../../../property-presets/number/utils/formats.js';
import { DEFAULT_COLUMN_TITLE_HEIGHT } from '../consts.js';
import type { TableColumn, TableSingleView } from '../table-view-manager.js';
import type { TableProperty, TableSingleView } from '../table-view-manager.js';
export class MobileTableColumnHeader extends SignalWatcher(
WithDisposable(ShadowlessElement)
@@ -174,16 +174,14 @@ export class MobileTableColumnHeader extends SignalWatcher(
menu.action({
name: 'Move Left',
prefix: MoveLeftIcon(),
hide: () => this.column.isFirst,
hide: () => this.column.isFirst$.value,
select: () => {
const preId = this.tableViewManager.propertyPreGet(
this.column.id
)?.id;
if (!preId) {
const pre = this.column.prev$.value;
if (!pre) {
return;
}
this.tableViewManager.propertyMove(this.column.id, {
id: preId,
this.column.move({
id: pre.id,
before: true,
});
},
@@ -191,16 +189,14 @@ export class MobileTableColumnHeader extends SignalWatcher(
menu.action({
name: 'Move Right',
prefix: MoveRightIcon(),
hide: () => this.column.isLast,
hide: () => this.column.isLast$.value,
select: () => {
const nextId = this.tableViewManager.propertyNextGet(
this.column.id
)?.id;
if (!nextId) {
const next = this.column.next$.value;
if (!next) {
return;
}
this.tableViewManager.propertyMove(this.column.id, {
id: nextId,
this.column.move({
id: next.id,
before: false,
});
},
@@ -256,7 +252,7 @@ export class MobileTableColumnHeader extends SignalWatcher(
}
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor tableViewManager!: TableSingleView;

View File

@@ -14,6 +14,7 @@ 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 { GroupData } 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';
@@ -100,15 +101,15 @@ export class MobileTableGroup extends SignalWatcher(
name: 'Ungroup',
hide: () => group.value == null,
select: () => {
group.rows.forEach(id => {
group.manager.removeFromGroup(id, group.key);
group.rows.forEach(row => {
group.manager.removeFromGroup(row.rowId, group.key);
});
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowDelete(group.rows);
this.view.rowsDelete(group.rows.map(row => row.rowId));
},
}),
]);
@@ -135,7 +136,7 @@ export class MobileTableGroup extends SignalWatcher(
return this.group?.rows ?? this.view.rows$.value;
}
private renderRows(ids: string[]) {
private renderRows(rows: Row[]) {
return html`
<mobile-table-header
.renderGroupHeader="${this.renderGroupHeader}"
@@ -143,15 +144,15 @@ export class MobileTableGroup extends SignalWatcher(
></mobile-table-header>
<div class="mobile-affine-table-body">
${repeat(
ids,
id => id,
(id, idx) => {
rows,
row => row.rowId,
(row, idx) => {
return html` <mobile-table-row
data-row-index="${idx}"
data-row-id="${id}"
data-row-id="${row.rowId}"
.dataViewEle="${this.dataViewEle}"
.view="${this.view}"
.rowId="${id}"
.rowId="${row.rowId}"
.rowIndex="${idx}"
></mobile-table-row>`;
}

View File

@@ -37,7 +37,7 @@ export const popMobileRowMenu = (
class: { 'delete-item': true },
prefix: DeleteIcon(),
select: () => {
view.rowDelete([rowId]);
view.rowsDelete([rowId]);
},
}),
],

View File

@@ -30,7 +30,7 @@ export class TableClipboardController implements ReactiveController {
.map(row => row.cells.map(cell => cell.stringValue$.value).join('\t'))
.join('\n');
const jsonResult: JsonAreaData = area.map(row =>
row.cells.map(cell => cell.stringValue$.value)
row.cells.map(cell => cell.stringValue$.value ?? '')
);
if (isCut) {
const deleteRows: string[] = [];
@@ -44,7 +44,7 @@ export class TableClipboardController implements ReactiveController {
}
}
if (deleteRows.length) {
this.props.view.rowDelete(deleteRows);
this.props.view.rowsDelete(deleteRows);
}
}
this.clipboard
@@ -210,7 +210,7 @@ function getSelectedArea(
.sort((a, b) => a.y - b.y)
.map(v => v.row);
return rows.map(r => {
const row = view.rowGet(r.id);
const row = view.rowGetOrCreate(r.id);
return {
row,
cells: row.cells$.value,
@@ -227,11 +227,11 @@ function getSelectedArea(
return;
}
for (let i = rowsSelection.start; i <= rowsSelection.end; i++) {
const row: SelectedArea[number] = {
const rowArea: SelectedArea[number] = {
cells: [],
};
const rowId = rows[i];
if (rowId == null) {
const row = rows[i];
if (row == null) {
continue;
}
for (let j = columnsSelection.start; j <= columnsSelection.end; j++) {
@@ -239,10 +239,10 @@ function getSelectedArea(
if (columnId == null) {
continue;
}
const cell = view.cellGet(rowId, columnId);
row.cells.push(cell);
const cell = view.cellGetOrCreate(row.rowId, columnId);
rowArea.cells.push(cell);
}
data.push(row);
data.push(rowArea);
}
return data;

View File

@@ -95,7 +95,7 @@ export function fillSelectionWithFocusCellData(
const curCell = cellContainer.cell$.value;
if (t.richText.is(curCol.dataType$.value)) {
if (curCol.dataType$.value && t.richText.is(curCol.dataType$.value)) {
const focusCellText = focusData as Text | undefined;
const delta = focusCellText?.toDelta() ?? [{ insert: '' }];

View File

@@ -25,7 +25,7 @@ export class TableHotkeysController implements ReactiveController {
if (TableViewRowSelection.is(selection)) {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.host.props.view.rowDelete(rows);
this.host.props.view.rowsDelete(rows);
return;
}
const {
@@ -336,11 +336,14 @@ export class TableHotkeysController implements ReactiveController {
rows:
this.host.props.view.groupTrait.groupsDataList$.value?.flatMap(
group =>
group?.rows.map(id => ({ groupKey: group.key, id })) ?? []
group?.rows.map(row => ({
groupKey: group.key,
id: row.rowId,
})) ?? []
) ??
this.host.props.view.rows$.value.map(id => ({
this.host.props.view.rows$.value.map(row => ({
groupKey: undefined,
id,
id: row.rowId,
})),
});
return true;

View File

@@ -242,7 +242,7 @@ export class TableSelectionController implements ReactiveController {
this.selection = TableViewAreaSelection.create({
groupKey: groupKey,
focus: {
rowIndex: rows?.findIndex(v => v === id) ?? 0,
rowIndex: rows?.findIndex(v => v.rowId === id) ?? 0,
columnIndex: index,
},
isEditing: true,
@@ -373,7 +373,7 @@ export class TableSelectionController implements ReactiveController {
}
deleteRow(rowId: string) {
this.view.rowDelete([rowId]);
this.view.rowsDelete([rowId]);
this.focusToCell('up');
}

View File

@@ -18,7 +18,7 @@ import { typeSystem } from '../../../../../../core';
import type { GroupData } from '../../../../../../core/group-by/trait';
import { statsFunctions } from '../../../../../../core/statistics';
import type { StatisticsConfig } from '../../../../../../core/statistics/types';
import type { TableColumn } from '../../../../table-view-manager';
import type { TableProperty } from '../../../../table-view-manager';
const styles = css`
.stats-cell {
@@ -73,12 +73,12 @@ export class VirtualDatabaseColumnStatsCell extends SignalWatcher(
static override styles = styles;
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
cellValues$ = computed(() => {
if (this.group) {
return this.group.rows.map(id => {
return this.column.valueGet(id);
return this.group.rows.map(row => {
return this.column.valueGet(row.rowId);
});
}
return this.column.cells$.value.map(cell => cell.jsonValue$.value);
@@ -157,7 +157,7 @@ export class VirtualDatabaseColumnStatsCell extends SignalWatcher(
values$ = signal<unknown[]>([]);
statsResult$ = computed(() => {
const meta = this.column.view.propertyMetaGet(this.column.type$.value);
const meta = this.column.meta$.value;
if (!meta) {
return null;
}

View File

@@ -86,15 +86,15 @@ export class TableGroupHeader extends SignalWatcher(
name: 'Ungroup',
hide: () => group.value == null,
select: () => {
group.rows.forEach(id => {
group.manager.removeFromGroup(id, group.key);
group.rows.forEach(row => {
group.manager.removeFromGroup(row.rowId, group.key);
});
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.tableViewManager.rowDelete(group.rows);
this.tableViewManager.rowsDelete(group.rows.map(row => row.rowId));
},
}),
]);

View File

@@ -48,7 +48,11 @@ export const GroupTitle = (
value: groupData.value,
data: groupData.property.data$.value,
updateData: groupData.manager.updateData,
updateValue: value => groupData.manager.updateValue(groupData.rows, value),
updateValue: value =>
groupData.manager.updateValue(
groupData.rows.map(row => row.rowId),
value
),
readonly: ops.readonly,
};

View File

@@ -1,11 +1,7 @@
import { baseTheme } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { globalStyle, style } from '@vanilla-extract/css';
import {
DEFAULT_ADD_BUTTON_WIDTH,
DEFAULT_COLUMN_TITLE_HEIGHT,
} from '../../../../consts';
import { DEFAULT_COLUMN_TITLE_HEIGHT } from '../../../../consts';
export const columnHeaderContainer = style({
display: 'block',
@@ -33,104 +29,6 @@ export const cell = style({
userSelect: 'none',
});
export const addColumnButton = style({
flex: 1,
minWidth: `${DEFAULT_ADD_BUTTON_WIDTH}px`,
minHeight: '100%',
display: 'flex',
alignItems: 'center',
});
export const columnContent = style({
display: 'flex',
alignItems: 'center',
gap: '6px',
width: '100%',
height: '100%',
padding: '6px',
boxSizing: 'border-box',
position: 'relative',
});
export const columnText = style({
flex: 1,
display: 'flex',
alignItems: 'center',
gap: '6px',
overflow: 'hidden',
color: 'var(--affine-text-secondary-color)',
fontSize: '14px',
position: 'relative',
});
export const columnTypeIcon = style({
display: 'flex',
alignItems: 'center',
borderRadius: '4px',
padding: '2px',
fontSize: '18px',
color: cssVarV2.icon.primary,
});
export const columnTextContent = style({
flex: 1,
display: 'flex',
alignItems: 'center',
overflow: 'hidden',
});
export const columnTextInput = style({
flex: 1,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontWeight: 500,
});
export const columnTextIcon = style({
display: 'flex',
alignItems: 'center',
width: '16px',
height: '16px',
background: 'var(--affine-white)',
border: `1px solid ${cssVarV2.layer.insideBorder.border}`,
borderRadius: '4px',
opacity: 0,
});
export const columnTextSaveIcon = style({
display: 'flex',
alignItems: 'center',
width: '16px',
height: '16px',
border: '1px solid transparent',
borderRadius: '4px',
fill: 'var(--affine-icon-color)',
selectors: {
'&:hover': {
background: 'var(--affine-white)',
borderColor: cssVarV2.layer.insideBorder.border,
},
},
});
export const columnInput = style({
width: '100%',
height: '24px',
padding: 0,
border: 'none',
color: 'inherit',
fontWeight: 600,
fontSize: '14px',
fontFamily: baseTheme.fontSansFamily,
background: 'transparent',
selectors: {
'&:focus': {
outline: 'none',
},
},
});
export const columnMove = style({
display: 'flex',
alignItems: 'center',
@@ -156,17 +54,6 @@ globalStyle(`${columnMove} svg`, {
opacity: 0,
});
export const databaseAddColumnButton = style({
position: 'sticky',
right: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '40px',
height: '38px',
cursor: 'pointer',
});
export const headerAddColumnButton = style({
height: `${DEFAULT_COLUMN_TITLE_HEIGHT}px`,
backgroundColor: 'var(--affine-background-primary-color)',
@@ -178,18 +65,3 @@ export const headerAddColumnButton = style({
fontSize: '18px',
color: cssVarV2.icon.primary,
});
export const columnTypeMenuIcon = style({
border: `1px solid ${cssVarV2.layer.insideBorder.border}`,
borderRadius: '4px',
padding: '5px',
backgroundColor: 'var(--affine-background-secondary-color)',
});
export const columnMovePreview = style({
position: 'fixed',
zIndex: 100,
width: '100px',
height: '100px',
background: 'var(--affine-text-emphasis-color)',
});

View File

@@ -30,8 +30,6 @@ export class VirtualTableHeader extends SignalWatcher(
column.editTitle();
};
preMove = 0;
private get readonly() {
return this.tableViewManager.readonly$.value;
}
@@ -41,10 +39,6 @@ export class VirtualTableHeader extends SignalWatcher(
this.classList.add(styles.columnHeaderContainer);
}
getScale() {
return this.scaleDiv?.getBoundingClientRect().width ?? 1;
}
override render() {
return html`
<div class="${styles.columnHeader} database-row">

View File

@@ -8,8 +8,9 @@ import { styleMap } from 'lit/directives/style-map.js';
import { html } from 'lit/static-html.js';
import type { GroupData } from '../../../../../../core/group-by/trait';
import type { Row } from '../../../../../../core/view-manager/row';
import type {
TableColumn,
TableProperty,
TableSingleView,
} from '../../../../table-view-manager';
@@ -29,8 +30,8 @@ export class DataViewColumnPreview extends SignalWatcher(
return this.column.view as TableSingleView;
}
private renderGroup(rows: string[]) {
const columnIndex = this.tableViewManager.propertyIndexGet(this.column.id);
private renderGroup(rows: Row[]) {
const columnIndex = this.column.index$.value;
return html`
<div
style="background-color: var(--affine-background-primary-color);border-top: 1px solid ${unsafeCSS(
@@ -77,7 +78,7 @@ export class DataViewColumnPreview extends SignalWatcher(
}
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor container!: HTMLElement;

View File

@@ -43,7 +43,7 @@ import { numberFormats } from '../../../../../../property-presets/number/utils/f
import { ShowQuickSettingBarContextKey } from '../../../../../../widget-presets/quick-setting-bar/context';
import { DEFAULT_COLUMN_TITLE_HEIGHT } from '../../../../consts';
import type {
TableColumn,
TableProperty,
TableSingleView,
} from '../../../../table-view-manager';
import {
@@ -86,9 +86,7 @@ export class DatabaseHeaderColumn extends SignalWatcher(
return menu.action({
name: config.config.name,
isSelected: config.type === this.column.type$.value,
prefix: renderUniLit(
this.tableViewManager.propertyIconGet(config.type)
),
prefix: renderUniLit(config.renderer.icon),
select: () => {
this.column.typeSet?.(config.type);
},
@@ -326,16 +324,14 @@ export class DatabaseHeaderColumn extends SignalWatcher(
menu.action({
name: 'Move Left',
prefix: MoveLeftIcon(),
hide: () => this.column.isFirst,
hide: () => this.column.isFirst$.value,
select: () => {
const preId = this.tableViewManager.propertyPreGet(
this.column.id
)?.id;
if (!preId) {
const pre = this.column.prev$.value;
if (!pre) {
return;
}
this.tableViewManager.propertyMove(this.column.id, {
id: preId,
this.column.move({
id: pre.id,
before: true,
});
},
@@ -343,16 +339,14 @@ export class DatabaseHeaderColumn extends SignalWatcher(
menu.action({
name: 'Move Right',
prefix: MoveRightIcon(),
hide: () => this.column.isLast,
hide: () => this.column.isLast$.value,
select: () => {
const nextId = this.tableViewManager.propertyNextGet(
this.column.id
)?.id;
if (!nextId) {
const next = this.column.next$.value;
if (!next) {
return;
}
this.tableViewManager.propertyMove(this.column.id, {
id: nextId,
this.column.move({
id: next.id,
before: false,
});
},
@@ -475,7 +469,7 @@ export class DatabaseHeaderColumn extends SignalWatcher(
}
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor grabStatus: 'grabStart' | 'grabEnd' | 'grabbing' = 'grabEnd';

View File

@@ -8,7 +8,7 @@ import { styleMap } from 'lit/directives/style-map.js';
import { startDrag } from '../../../../../../core/utils/drag';
import { getResultInRange } from '../../../../../../core/utils/utils';
import type { TableColumn } from '../../../../table-view-manager';
import type { TableProperty } from '../../../../table-view-manager';
export class TableVerticalIndicator extends WithDisposable(ShadowlessElement) {
static override styles = css`
@@ -101,7 +101,7 @@ export const startDragWidthAdjustmentBar = (
evt: PointerEvent,
ele: HTMLElement,
width: number,
column: TableColumn
column: TableProperty
) => {
const scale = width / column.width$.value;
const left = ele.getBoundingClientRect().left;

View File

@@ -45,7 +45,7 @@ export class DatabaseCellContainer extends SignalWatcher(
private readonly _cell = signal<DataViewCellLifeCycle>();
cell$ = computed(() => {
return this.view.cellGet(this.rowId, this.columnId);
return this.view.cellGetOrCreate(this.rowId, this.columnId);
});
selectCurrentCell = (editing: boolean) => {

View File

@@ -70,7 +70,7 @@ export const popRowMenu = (
},
prefix: DeleteIcon(),
select: () => {
selectionController.view.rowDelete(rows);
selectionController.view.rowsDelete(rows);
},
}),
],

View File

@@ -134,12 +134,8 @@ export class VirtualTableView extends DataViewBase<
moveTo: (id, evt) => {
const result = this.dragController.getInsertPosition(evt);
if (result) {
this.props.view.rowMove(
id,
result.position,
undefined,
result.groupKey
);
const row = this.props.view.rowGetOrCreate(id);
row.move(result.position, undefined, result.groupKey);
}
},
getSelection: () => {
@@ -181,13 +177,13 @@ export class VirtualTableView extends DataViewBase<
return [
{
id: '',
rows: this.props.view.rows$.value,
rows: this.props.view.rowIds$.value,
},
];
}
return groupTrait.groupsDataList$.value.map(group => ({
id: group.key,
rows: group.rows,
rows: group.rows.map(v => v.rowId),
}));
});
virtualScroll$ = signal<TableGrid>();
@@ -221,9 +217,10 @@ export class VirtualTableView extends DataViewBase<
if (!selection || selection.selectionType !== 'row') {
return false;
}
const groupId = row.group.groupId;
return TableViewRowSelection.includes(selection, {
id: row.rowId,
groupKey: row.group.groupId,
groupKey: groupId ? groupId : undefined,
});
}),
}),

View File

@@ -14,7 +14,7 @@ import {
TableViewAreaSelection,
type TableViewSelectionWithType,
} from '../selection';
import type { TableColumn } from '../table-view-manager.js';
import type { TableProperty } from '../table-view-manager.js';
import type { TableGroup } from './group.js';
export class DatabaseCellContainer extends SignalWatcher(
@@ -42,13 +42,13 @@ export class DatabaseCellContainer extends SignalWatcher(
private readonly _cell = signal<DataViewCellLifeCycle>();
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor rowId!: string;
cell$ = computed(() => {
return this.column.cellGet(this.rowId);
return this.column.cellGetOrCreate(this.rowId);
});
selectCurrentCell = (editing: boolean) => {

View File

@@ -30,7 +30,7 @@ export class TableClipboardController implements ReactiveController {
.map(row => row.cells.map(cell => cell.stringValue$.value).join('\t'))
.join('\n');
const jsonResult: JsonAreaData = area.map(row =>
row.cells.map(cell => cell.stringValue$.value)
row.cells.map(cell => cell.stringValue$.value ?? '')
);
if (isCut) {
const deleteRows: string[] = [];
@@ -44,7 +44,7 @@ export class TableClipboardController implements ReactiveController {
}
}
if (deleteRows.length) {
this.props.view.rowDelete(deleteRows);
this.props.view.rowsDelete(deleteRows);
}
}
this.clipboard
@@ -211,7 +211,7 @@ function getSelectedArea(
.sort((a, b) => a.y - b.y)
.map(v => v.row);
return rows.map(r => {
const row = view.rowGet(r.id);
const row = view.rowGetOrCreate(r.id);
return {
row,
cells: row.cells$.value,
@@ -228,11 +228,11 @@ function getSelectedArea(
return;
}
for (let i = rowsSelection.start; i <= rowsSelection.end; i++) {
const row: SelectedArea[number] = {
const rowArea: SelectedArea[number] = {
cells: [],
};
const rowId = rows[i];
if (rowId == null) {
const row = rows[i];
if (row == null) {
continue;
}
for (let j = columnsSelection.start; j <= columnsSelection.end; j++) {
@@ -240,10 +240,10 @@ function getSelectedArea(
if (columnId == null) {
continue;
}
const cell = view.cellGet(rowId, columnId);
row.cells.push(cell);
const cell = view.cellGetOrCreate(row.rowId, columnId);
rowArea.cells.push(cell);
}
data.push(row);
data.push(rowArea);
}
return data;

View File

@@ -88,7 +88,9 @@ export function fillSelectionWithFocusCellData(
const curCell = cellContainer.cell$.value;
if (t.richText.is(curCol.dataType$.value)) {
const dataType = curCol.dataType$.value;
if (dataType && t.richText.is(dataType)) {
const focusCellText = focusData as Text | undefined;
const delta = focusCellText?.toDelta() ?? [{ insert: '' }];

View File

@@ -4,20 +4,20 @@ import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
import type { ReactiveController } from 'lit';
import { startDrag } from '../../../../core/utils/drag.js';
import { TableRow } from '../row/row.js';
import { TableRowView } from '../row/row.js';
import type { DataViewTable } from '../table-view.js';
export class TableDragController implements ReactiveController {
dragStart = (row: TableRow, evt: PointerEvent) => {
const eleRect = row.getBoundingClientRect();
dragStart = (rowView: TableRowView, evt: PointerEvent) => {
const eleRect = rowView.getBoundingClientRect();
const offsetLeft = evt.x - eleRect.left;
const offsetTop = evt.y - eleRect.top;
const preview = createDragPreview(
row,
rowView,
evt.x - offsetLeft,
evt.y - offsetTop
);
const fromGroup = row.groupKey;
const fromGroup = rowView.groupKey;
startDrag<
| undefined
@@ -38,7 +38,7 @@ export class TableDragController implements ReactiveController {
this.dropPreview.remove();
return {
type: 'out',
callback: callback(evt, row.rowId),
callback: callback(evt, rowView.rowId),
};
}
return;
@@ -66,12 +66,8 @@ export class TableDragController implements ReactiveController {
return;
}
if (result.type === 'self') {
this.host.props.view.rowMove(
row.rowId,
result.position,
fromGroup,
result.groupKey
);
const row = this.host.props.view.rowGetOrCreate(rowView.rowId);
row.move(result.position, fromGroup, result.groupKey);
}
},
});
@@ -159,9 +155,9 @@ export class TableDragController implements ReactiveController {
}
}
const createDragPreview = (row: TableRow, x: number, y: number) => {
const createDragPreview = (row: TableRowView, x: number, y: number) => {
const div = document.createElement('div');
const cloneRow = new TableRow();
const cloneRow = new TableRowView();
cloneRow.view = row.view;
cloneRow.rowIndex = row.rowIndex;
cloneRow.rowId = row.rowId;

View File

@@ -25,7 +25,7 @@ export class TableHotkeysController implements ReactiveController {
if (TableViewRowSelection.is(selection)) {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.host.props.view.rowDelete(rows);
this.host.props.view.rowsDelete(rows);
return;
}
const {
@@ -336,11 +336,14 @@ export class TableHotkeysController implements ReactiveController {
rows:
this.host.props.view.groupTrait.groupsDataList$.value?.flatMap(
group =>
group?.rows.map(id => ({ groupKey: group.key, id })) ?? []
group?.rows.map(row => ({
groupKey: group.key,
id: row.rowId,
})) ?? []
) ??
this.host.props.view.rows$.value.map(id => ({
this.host.props.view.rows$.value.map(row => ({
groupKey: undefined,
id,
id: row.rowId,
})),
});
return true;

View File

@@ -20,7 +20,7 @@ import {
} from '../../selection';
import type { DatabaseCellContainer } from '../cell.js';
import type { TableGroup } from '../group.js';
import type { TableRow } from '../row/row.js';
import type { TableRowView } from '../row/row.js';
import type { DataViewTable } from '../table-view.js';
import {
DragToFillElement,
@@ -244,7 +244,7 @@ export class TableSelectionController implements ReactiveController {
this.selection = TableViewAreaSelection.create({
groupKey: groupKey,
focus: {
rowIndex: rows?.findIndex(v => v === id) ?? 0,
rowIndex: rows?.findIndex(v => v.rowId === id) ?? 0,
columnIndex: index,
},
isEditing: true,
@@ -346,7 +346,7 @@ export class TableSelectionController implements ReactiveController {
}
deleteRow(rowId: string) {
this.view.rowDelete([rowId]);
this.view.rowsDelete([rowId]);
this.focusToCell('up');
}
@@ -553,7 +553,7 @@ export class TableSelectionController implements ReactiveController {
(
this.getGroup(lastRow?.groupKey)?.querySelector(
`data-view-table-row[data-row-id='${lastRow?.id}']`
) as TableRow | null
) as TableRowView | null
)?.rowIndex ?? 0;
const getRowByIndex = (index: number) => {
const tableRow = this.rows(lastRow?.groupKey)?.item(index);

View File

@@ -7,7 +7,7 @@ import { DataViewColumnPreview } from './header/column-renderer.js';
import { DatabaseHeaderColumn } from './header/database-header-column.js';
import { DatabaseNumberFormatBar } from './header/number-format-bar.js';
import { TableVerticalIndicator } from './header/vertical-indicator.js';
import { TableRow } from './row/row.js';
import { TableRowView } from './row/row.js';
import { RowSelectCheckbox } from './row/row-select-checkbox.js';
import { DataViewTable } from './table-view.js';
@@ -28,7 +28,7 @@ export function pcEffects() {
'affine-database-number-format-bar',
DatabaseNumberFormatBar
);
customElements.define('data-view-table-row', TableRow);
customElements.define('data-view-table-row', TableRowView);
customElements.define('row-select-checkbox', RowSelectCheckbox);
customElements.define('data-view-table-selection', SelectionElement);
customElements.define('data-view-drag-to-fill', DragToFillElement);

View File

@@ -15,6 +15,7 @@ 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 { GroupData } from '../../../core/group-by/trait.js';
import type { Row } from '../../../core/index.js';
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';
@@ -117,15 +118,15 @@ export class TableGroup extends SignalWatcher(
name: 'Ungroup',
hide: () => group.value == null,
select: () => {
group.rows.forEach(id => {
group.manager.removeFromGroup(id, group.key);
group.rows.forEach(row => {
group.manager.removeFromGroup(row.rowId, group.key);
});
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowDelete(group.rows);
this.view.rowsDelete(group.rows.map(row => row.rowId));
},
}),
]);
@@ -173,7 +174,7 @@ export class TableGroup extends SignalWatcher(
const overIndex = this.view.properties$.value.findIndex(
data => data.id === over.id
);
this.view.propertyMove(active.id, {
this.view.propertyGetOrCreate(active.id).move({
before: activeIndex > overIndex,
id: over.id,
});
@@ -181,7 +182,7 @@ export class TableGroup extends SignalWatcher(
},
collisionDetection: linearMove(true),
createOverlay: active => {
const column = this.view.propertyGet(active.id);
const column = this.view.propertyGetOrCreate(active.id);
const preview = new DataViewColumnPreview();
preview.column = column;
preview.group = this.group;
@@ -241,7 +242,7 @@ export class TableGroup extends SignalWatcher(
return this.group?.rows ?? this.view.rows$.value;
}
private renderRows(ids: string[]) {
private renderRows(rows: Row[]) {
return html`
<affine-database-column-header
.renderGroupHeader="${this.renderGroupHeader}"
@@ -249,15 +250,15 @@ export class TableGroup extends SignalWatcher(
></affine-database-column-header>
<div class="affine-database-block-rows">
${repeat(
ids,
id => id,
(id, idx) => {
rows,
row => row.rowId,
(row, idx) => {
return html` <data-view-table-row
data-row-index="${idx}"
data-row-id="${id}"
data-row-id="${row.rowId}"
.dataViewEle="${this.dataViewEle}"
.view="${this.view}"
.rowId="${id}"
.rowId="${row.rowId}"
.rowIndex="${idx}"
></data-view-table-row>`;
}

View File

@@ -8,7 +8,11 @@ import { styleMap } from 'lit/directives/style-map.js';
import { html } from 'lit/static-html.js';
import type { GroupData } from '../../../../core/group-by/trait.js';
import type { TableColumn, TableSingleView } from '../../table-view-manager.js';
import type { Row } from '../../../../core/index.js';
import type {
TableProperty,
TableSingleView,
} from '../../table-view-manager.js';
export class DataViewColumnPreview extends SignalWatcher(
WithDisposable(ShadowlessElement)
@@ -26,8 +30,8 @@ export class DataViewColumnPreview extends SignalWatcher(
return this.column.view as TableSingleView;
}
private renderGroup(rows: string[]) {
const columnIndex = this.tableViewManager.propertyIndexGet(this.column.id);
private renderGroup(rows: Row[]) {
const columnIndex = this.column.index$.value;
return html`
<div
style="background-color: var(--affine-background-primary-color);border-top: 1px solid ${unsafeCSS(
@@ -74,7 +78,7 @@ export class DataViewColumnPreview extends SignalWatcher(
}
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor container!: HTMLElement;

View File

@@ -42,7 +42,10 @@ import type { Property } from '../../../../core/view-manager/property.js';
import { numberFormats } from '../../../../property-presets/number/utils/formats.js';
import { ShowQuickSettingBarContextKey } from '../../../../widget-presets/quick-setting-bar/context.js';
import { DEFAULT_COLUMN_TITLE_HEIGHT } from '../../consts.js';
import type { TableColumn, TableSingleView } from '../../table-view-manager.js';
import type {
TableProperty,
TableSingleView,
} from '../../table-view-manager.js';
import {
getTableGroupRect,
getVerticalIndicator,
@@ -83,9 +86,7 @@ export class DatabaseHeaderColumn extends SignalWatcher(
return menu.action({
name: config.config.name,
isSelected: config.type === this.column.type$.value,
prefix: renderUniLit(
this.tableViewManager.propertyIconGet(config.type)
),
prefix: renderUniLit(config.renderer.icon),
select: () => {
this.column.typeSet?.(config.type);
},
@@ -323,16 +324,14 @@ export class DatabaseHeaderColumn extends SignalWatcher(
menu.action({
name: 'Move Left',
prefix: MoveLeftIcon(),
hide: () => this.column.isFirst,
hide: () => this.column.isFirst$.value,
select: () => {
const preId = this.tableViewManager.propertyPreGet(
this.column.id
)?.id;
if (!preId) {
const prev = this.column.prev$.value;
if (!prev) {
return;
}
this.tableViewManager.propertyMove(this.column.id, {
id: preId,
this.column.move({
id: prev.id,
before: true,
});
},
@@ -340,16 +339,14 @@ export class DatabaseHeaderColumn extends SignalWatcher(
menu.action({
name: 'Move Right',
prefix: MoveRightIcon(),
hide: () => this.column.isLast,
hide: () => this.column.isLast$.value,
select: () => {
const nextId = this.tableViewManager.propertyNextGet(
this.column.id
)?.id;
if (!nextId) {
const next = this.column.next$.value;
if (!next) {
return;
}
this.tableViewManager.propertyMove(this.column.id, {
id: nextId,
this.column.move({
id: next.id,
before: false,
});
},
@@ -472,7 +469,7 @@ export class DatabaseHeaderColumn extends SignalWatcher(
}
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
@property({ attribute: false })
accessor grabStatus: 'grabStart' | 'grabEnd' | 'grabbing' = 'grabEnd';

View File

@@ -8,7 +8,7 @@ import { styleMap } from 'lit/directives/style-map.js';
import { startDrag } from '../../../../core/utils/drag.js';
import { getResultInRange } from '../../../../core/utils/utils.js';
import type { TableColumn } from '../../table-view-manager.js';
import type { TableProperty } from '../../table-view-manager.js';
export class TableVerticalIndicator extends WithDisposable(ShadowlessElement) {
static override styles = css`
@@ -95,7 +95,7 @@ export const startDragWidthAdjustmentBar = (
evt: PointerEvent,
ele: HTMLElement,
width: number,
column: TableColumn
column: TableProperty
) => {
const scale = width / column.width$.value;
const left = ele.getBoundingClientRect().left;

View File

@@ -70,7 +70,7 @@ export const popRowMenu = (
},
prefix: DeleteIcon(),
select: () => {
selectionController.view.rowDelete(rows);
selectionController.view.rowsDelete(rows);
},
}),
],

View File

@@ -18,7 +18,9 @@ import type { TableSingleView } from '../../table-view-manager.js';
import type { TableGroup } from '../group.js';
import { openDetail, popRowMenu } from '../menu.js';
export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) {
export class TableRowView extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
.affine-database-block-row:has(.row-select-checkbox.selected) {
background: var(--affine-primary-color-04);
@@ -294,6 +296,6 @@ export class TableRow extends SignalWatcher(WithDisposable(ShadowlessElement)) {
declare global {
interface HTMLElementTagNameMap {
'data-view-table-row': TableRow;
'data-view-table-row': TableRowView;
}
}

View File

@@ -237,12 +237,8 @@ export class DataViewTable extends DataViewBase<
moveTo: (id, evt) => {
const result = this.dragController.getInsertPosition(evt);
if (result) {
this.props.view.rowMove(
id,
result.position,
undefined,
result.groupKey
);
const row = this.props.view.rowGetOrCreate(id);
row.move(result.position, undefined, result.groupKey);
}
},
getSelection: () => {

View File

@@ -18,7 +18,7 @@ import type { GroupData } from '../../../core/group-by/trait.js';
import { typeSystem } from '../../../core/index.js';
import { statsFunctions } from '../../../core/statistics/index.js';
import type { StatisticsConfig } from '../../../core/statistics/types.js';
import type { TableColumn } from '../table-view-manager.js';
import type { TableProperty } from '../table-view-manager.js';
const styles = css`
.stats-cell {
@@ -73,12 +73,12 @@ export class DatabaseColumnStatsCell extends SignalWatcher(
static override styles = styles;
@property({ attribute: false })
accessor column!: TableColumn;
accessor column!: TableProperty;
cellValues$ = computed(() => {
if (this.group) {
return this.group.rows.map(id => {
return this.column.valueGet(id);
return this.group.rows.map(row => {
return this.column.valueGet(row.rowId);
});
}
return this.column.cells$.value.map(cell => cell.jsonValue$.value);
@@ -157,7 +157,7 @@ export class DatabaseColumnStatsCell extends SignalWatcher(
values$ = signal<unknown[]>([]);
statsResult$ = computed(() => {
const meta = this.column.view.propertyMetaGet(this.column.type$.value);
const meta = this.column.meta$.value;
if (!meta) {
return null;
}

View File

@@ -17,6 +17,7 @@ import {
import { fromJson } from '../../core/property/utils';
import { SortManager, sortTraitKey } from '../../core/sort/manager.js';
import { PropertyBase } from '../../core/view-manager/property.js';
import { type Row, RowBase } from '../../core/view-manager/row.js';
import {
type SingleView,
SingleViewBase,
@@ -24,10 +25,9 @@ import {
import type { ViewManager } from '../../core/view-manager/view-manager.js';
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_COLUMN_WIDTH } from './consts.js';
import type { TableViewData } from './define.js';
import type { StatCalcOpType } from './types.js';
export class TableSingleView extends SingleViewBase<TableViewData> {
propertiesWithoutFilter$ = computed(() => {
propertiesRaw$ = computed(() => {
const needShow = new Set(this.dataSource.properties$.value);
const result: string[] = [];
this.data$.value?.columns.forEach(v => {
@@ -37,19 +37,11 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
}
});
result.push(...needShow);
return result;
return result.map(id => this.propertyGetOrCreate(id));
});
private readonly computedColumns$ = computed(() => {
return this.propertiesWithoutFilter$.value.map(id => {
const column = this.propertyGet(id);
return {
id: column.id,
hide: column.hide$.value,
width: column.width$.value,
statCalcType: column.statCalcOp$.value,
};
});
properties$ = computed(() => {
return this.propertiesRaw$.value.filter(property => !property.hide$.value);
});
private readonly filter$ = computed(() => {
@@ -81,8 +73,8 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
);
detailProperties$ = computed(() => {
return this.propertiesWithoutFilter$.value.filter(
id => this.propertyTypeGet(id) !== 'title'
return this.propertiesRaw$.value.filter(
property => property.type$.value !== 'title'
);
});
@@ -115,9 +107,13 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
v => v,
this.groupProperties.map(v => v.key)
),
sortRow: (key, ids) => {
sortRow: (key, rows) => {
const property = this.groupProperties.find(v => v.key === key);
return sortByManually(ids, v => v, property?.manuallyCardSort ?? []);
return sortByManually(
rows,
v => v.rowId,
property?.manuallyCardSort ?? []
);
},
changeGroupSort: keys => {
const map = new Map(this.groupProperties.map(v => [v.key, v]));
@@ -173,20 +169,14 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
mainProperties$ = computed(() => {
return (
this.data$.value?.header ?? {
titleColumn: this.propertiesWithoutFilter$.value.find(
id => this.propertyTypeGet(id) === 'title'
),
titleColumn: this.propertiesRaw$.value.find(
property => property.type$.value === 'title'
)?.id,
iconColumn: 'type',
}
);
});
propertyIds$ = computed(() => {
return this.propertiesWithoutFilter$.value.filter(
id => !this.propertyHideGet(id)
);
});
readonly$ = computed(() => {
return this.manager.readonly$.value;
});
@@ -207,58 +197,12 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
super(viewManager, viewId);
}
columnGetStatCalcOp(columnId: string): StatCalcOpType {
return this.data$.value?.columns.find(v => v.id === columnId)?.statCalcType;
}
columnGetWidth(columnId: string): number {
const column = this.data$.value?.columns.find(v => v.id === columnId);
if (column?.width != null) {
return column.width;
}
const type = this.propertyTypeGet(columnId);
if (type === 'title') {
return 260;
}
return DEFAULT_COLUMN_WIDTH;
}
columnUpdateStatCalcOp(columnId: string, op?: string): void {
this.dataUpdate(() => {
return {
columns: this.computedColumns$.value.map(v =>
v.id === columnId
? {
...v,
statCalcType: op,
}
: v
),
};
});
}
columnUpdateWidth(columnId: string, width: number): void {
this.dataUpdate(() => {
return {
columns: this.computedColumns$.value.map(v =>
v.id === columnId
? {
...v,
width: width,
}
: v
),
};
});
}
isShow(rowId: string): boolean {
if (this.filter$.value?.conditions.length) {
const rowMap = Object.fromEntries(
this.properties$.value.map(column => [
column.id,
column.cellGet(rowId).jsonValue$.value,
column.cellGetOrCreate(rowId).jsonValue$.value,
])
);
return evalFilter(this.filter$.value, rowMap);
@@ -266,54 +210,12 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
return true;
}
minWidthGet(type: string): number {
return (
this.propertyMetaGet(type)?.config.minWidth ?? DEFAULT_COLUMN_MIN_WIDTH
);
propertyGetOrCreate(columnId: string): TableProperty {
return new TableProperty(this, columnId);
}
propertyGet(columnId: string): TableColumn {
return new TableColumn(this, columnId);
}
propertyHideGet(columnId: string): boolean {
return (
this.data$.value?.columns.find(v => v.id === columnId)?.hide ?? false
);
}
propertyHideSet(columnId: string, hide: boolean): void {
this.dataUpdate(() => {
return {
columns: this.computedColumns$.value.map(v =>
v.id === columnId
? {
...v,
hide,
}
: v
),
};
});
}
propertyMove(columnId: string, toAfterOfColumn: InsertToPosition): void {
this.dataUpdate(() => {
const columnIndex = this.computedColumns$.value.findIndex(
v => v.id === columnId
);
if (columnIndex < 0) {
return {};
}
const columns = [...this.computedColumns$.value];
const [column] = columns.splice(columnIndex, 1);
if (!column) return {};
const index = insertPositionToIndex(toAfterOfColumn, columns);
columns.splice(index, 0, column);
return {
columns,
};
});
override rowGetOrCreate(rowId: string): TableRow {
return new TableRow(this, rowId);
}
override rowAdd(
@@ -326,15 +228,15 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
if (filter.conditions.length > 0) {
const defaultValues = generateDefaultValues(filter, this.vars$.value);
Object.entries(defaultValues).forEach(([propertyId, jsonValue]) => {
const property = this.propertyGet(propertyId);
const propertyMeta = this.propertyMetaGet(property.type$.value);
const property = this.propertyGetOrCreate(propertyId);
const propertyMeta = property.meta$.value;
if (propertyMeta) {
const value = fromJson(propertyMeta.config, {
value: jsonValue,
data: property.data$.value,
dataSource: this.dataSource,
});
this.cellValueSet(id, propertyId, value);
this.cellGetOrCreate(id, propertyId).valueSet(value);
}
});
}
@@ -345,45 +247,84 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
return id;
}
override rowMove(
rowId: string,
position: InsertToPosition,
fromGroup?: string,
toGroup?: string
) {
if (toGroup == null) {
super.rowMove(rowId, position);
return;
}
this.groupTrait.moveCardTo(rowId, fromGroup, toGroup, position);
}
override rowNextGet(rowId: string): string | undefined {
const index = this.rows$.value.indexOf(rowId);
return this.rows$.value[index + 1];
}
override rowPrevGet(rowId: string): string | undefined {
const index = this.rows$.value.indexOf(rowId);
return this.rows$.value[index - 1];
}
override rowsMapping(rows: string[]) {
override rowsMapping(rows: Row[]) {
return this.sortManager.sort(super.rowsMapping(rows));
}
readonly computedProperties$: ReadonlySignal<TableColumnData[]> = computed(
() => {
return this.propertiesRaw$.value.map(property => {
return {
id: property.id,
hide: property.hide$.value,
width: property.width$.value,
statCalcType: property.statCalcOp$.value,
};
});
}
);
}
export class TableColumn extends PropertyBase {
type TableColumnData = TableViewData['columns'][number];
export class TableProperty extends PropertyBase {
override hideSet(hide: boolean): void {
this.viewDataUpdate(data => {
return {
...data,
hide,
};
});
}
override move(position: InsertToPosition): void {
this.tableView.dataUpdate(() => {
const columnIndex = this.tableView.computedProperties$.value.findIndex(
v => v.id === this.id
);
if (columnIndex < 0) {
return {};
}
const columns = [...this.tableView.computedProperties$.value];
const [column] = columns.splice(columnIndex, 1);
if (!column) return {};
const index = insertPositionToIndex(position, columns);
columns.splice(index, 0, column);
return {
columns,
};
});
}
hide$ = computed(() => {
const hideFromViewData = this.viewData$.value?.hide;
if (hideFromViewData != null) {
return hideFromViewData;
}
const defaultShow = this.meta$.value?.config.fixed?.defaultShow;
if (defaultShow != null) {
return !defaultShow;
}
return false;
});
statCalcOp$ = computed(() => {
return this.tableView.columnGetStatCalcOp(this.id);
return this.viewData$.value?.statCalcType;
});
width$: ReadonlySignal<number> = computed(() => {
return this.tableView.columnGetWidth(this.id);
const column = this.viewData$.value;
if (column?.width != null) {
return column.width;
}
const type = this.type$.value;
if (type === 'title') {
return 260;
}
return DEFAULT_COLUMN_WIDTH;
});
get minWidth() {
return this.tableView.minWidthGet(this.type$.value);
return this.meta$.value?.config.minWidth ?? DEFAULT_COLUMN_MIN_WIDTH;
}
constructor(
@@ -393,11 +334,62 @@ export class TableColumn extends PropertyBase {
super(tableView as SingleView, columnId);
}
viewDataUpdate(
updater: (viewData: TableColumnData) => Partial<TableColumnData>
): void {
this.tableView.dataUpdate(data => {
return {
...data,
columns: this.tableView.computedProperties$.value.map(v =>
v.id === this.id ? { ...v, ...updater(v) } : v
),
};
});
}
viewData$ = computed(() => {
return this.tableView.data$.value?.columns.find(v => v.id === this.id);
});
updateStatCalcOp(type?: string): void {
return this.tableView.columnUpdateStatCalcOp(this.id, type);
this.viewDataUpdate(data => {
return {
...data,
statCalcType: type,
};
});
}
updateWidth(width: number): void {
this.tableView.columnUpdateWidth(this.id, width);
this.viewDataUpdate(data => {
return {
...data,
width,
};
});
}
}
export class TableRow extends RowBase {
override move(
position: InsertToPosition,
fromGroup?: string,
toGroup?: string
): void {
if (toGroup == null) {
super.move(position);
return;
}
this.tableView.groupTrait.moveCardTo(
this.rowId,
fromGroup,
toGroup,
position
);
}
constructor(
readonly tableView: TableSingleView,
rowId: string
) {
super(tableView, rowId);
}
}