mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 06:16:59 +08:00
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:
@@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export type PropertyConfig<Data, RawValue = unknown, JsonValue = unknown> = {
|
||||
};
|
||||
fixed?: {
|
||||
defaultData: Data;
|
||||
defaultOrder?: string;
|
||||
defaultOrder?: 'start' | 'end';
|
||||
defaultShow?: boolean;
|
||||
};
|
||||
minWidth?: number;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
15
blocksuite/affine/data-view/src/core/utils/lock.ts
Normal file
15
blocksuite/affine/data-view/src/core/utils/lock.ts
Normal 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;
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user