mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor(editor): add runtime type checks to database cell values (#10770)
This commit is contained in:
@@ -53,8 +53,11 @@ export interface DataSource {
|
||||
propertyReadonlyGet(propertyId: string): boolean;
|
||||
propertyReadonlyGet$(propertyId: string): ReadonlySignal<boolean>;
|
||||
|
||||
propertyMetaGet(type: string): PropertyMetaConfig;
|
||||
propertyAdd(insertToPosition: InsertToPosition, type?: string): string;
|
||||
propertyMetaGet(type: string): PropertyMetaConfig | undefined;
|
||||
propertyAdd(
|
||||
insertToPosition: InsertToPosition,
|
||||
type?: string
|
||||
): string | undefined;
|
||||
|
||||
propertyDuplicate(propertyId: string): string | undefined;
|
||||
propertyCanDuplicate(propertyId: string): boolean;
|
||||
@@ -152,7 +155,7 @@ export abstract class DataSourceBase implements DataSource {
|
||||
abstract propertyAdd(
|
||||
insertToPosition: InsertToPosition,
|
||||
type?: string
|
||||
): string;
|
||||
): string | undefined;
|
||||
|
||||
abstract propertyDataGet(propertyId: string): Record<string, unknown>;
|
||||
|
||||
@@ -179,7 +182,7 @@ export abstract class DataSourceBase implements DataSource {
|
||||
|
||||
abstract propertyDuplicate(propertyId: string): string | undefined;
|
||||
|
||||
abstract propertyMetaGet(type: string): PropertyMetaConfig;
|
||||
abstract propertyMetaGet(type: string): PropertyMetaConfig | undefined;
|
||||
|
||||
abstract propertyNameGet(propertyId: string): string;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const defaultGroupBy = (
|
||||
data: NonNullable<unknown>
|
||||
): GroupBy | undefined => {
|
||||
const name = groupByMatcher.match(
|
||||
propertyMeta.config.type({ data, dataSource })
|
||||
propertyMeta.config.jsonValue.type({ data, dataSource })
|
||||
)?.name;
|
||||
return name != null
|
||||
? {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { computed, type ReadonlySignal } from '@preact/signals-core';
|
||||
|
||||
import type { GroupBy, GroupProperty } from '../common/types.js';
|
||||
import type { TypeInstance } from '../logical/type.js';
|
||||
import type { DVJSON } from '../property/types.js';
|
||||
import { createTraitKey } from '../traits/key.js';
|
||||
import type { Property } from '../view-manager/property.js';
|
||||
import type { SingleView } from '../view-manager/single-view.js';
|
||||
@@ -19,7 +18,7 @@ export type GroupData = {
|
||||
key: string;
|
||||
name: string;
|
||||
type: TypeInstance;
|
||||
value: DVJSON;
|
||||
value: unknown;
|
||||
rows: string[];
|
||||
};
|
||||
|
||||
@@ -235,7 +234,7 @@ export class GroupTrait {
|
||||
}
|
||||
const remove = this.config$.value?.removeFromGroup ?? (() => null);
|
||||
const group = fromGroupKey != null ? groupMap[fromGroupKey] : undefined;
|
||||
let newValue: DVJSON = null;
|
||||
let newValue: unknown = null;
|
||||
if (group) {
|
||||
newValue = remove(
|
||||
group.value,
|
||||
@@ -284,7 +283,7 @@ export class GroupTrait {
|
||||
this.view.cellValueSet(rowId, propertyId, newValue);
|
||||
}
|
||||
|
||||
updateValue(rows: string[], value: DVJSON) {
|
||||
updateValue(rows: string[], value: unknown) {
|
||||
const propertyId = this.propertyId;
|
||||
if (!propertyId) {
|
||||
return;
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
|
||||
import type { TypeInstance } from '../logical/type.js';
|
||||
import type { DVJSON } from '../property/types.js';
|
||||
|
||||
export interface GroupRenderProps<
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
Value = DVJSON,
|
||||
JsonValue = unknown,
|
||||
> {
|
||||
data: Data;
|
||||
updateData?: (data: Data) => void;
|
||||
value: Value;
|
||||
updateValue?: (value: Value) => void;
|
||||
value: JsonValue;
|
||||
updateValue?: (value: JsonValue) => void;
|
||||
readonly: boolean;
|
||||
}
|
||||
|
||||
export type GroupByConfig = {
|
||||
export type GroupByConfig<
|
||||
JsonValue = unknown,
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
> = {
|
||||
name: string;
|
||||
groupName: (type: TypeInstance, value: unknown) => string;
|
||||
defaultKeys: (type: TypeInstance) => {
|
||||
key: string;
|
||||
value: DVJSON;
|
||||
value: JsonValue;
|
||||
}[];
|
||||
valuesGroup: (
|
||||
value: unknown,
|
||||
type: TypeInstance
|
||||
) => {
|
||||
key: string;
|
||||
value: DVJSON;
|
||||
value: JsonValue;
|
||||
}[];
|
||||
addToGroup?: (value: DVJSON, oldValue: DVJSON) => DVJSON;
|
||||
removeFromGroup?: (value: DVJSON, oldValue: DVJSON) => DVJSON;
|
||||
view: UniComponent<GroupRenderProps>;
|
||||
addToGroup?: (value: JsonValue, oldValue: JsonValue) => JsonValue;
|
||||
removeFromGroup?: (value: JsonValue, oldValue: JsonValue) => JsonValue;
|
||||
view: UniComponent<GroupRenderProps<Data, JsonValue>>;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ export const SelectTagSchema = Zod.object({
|
||||
id: Zod.string(),
|
||||
color: Zod.string(),
|
||||
value: Zod.string(),
|
||||
parentId: Zod.string().optional(),
|
||||
});
|
||||
export const unknown = defineDataType('Unknown', zod.never(), zod.unknown());
|
||||
export const dt = {
|
||||
|
||||
@@ -8,18 +8,19 @@ import type { Cell } from '../view-manager/cell.js';
|
||||
import type { CellRenderProps, DataViewCellLifeCycle } from './manager.js';
|
||||
|
||||
export abstract class BaseCellRenderer<
|
||||
Value,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>,
|
||||
>
|
||||
extends SignalWatcher(WithDisposable(ShadowlessElement))
|
||||
implements DataViewCellLifeCycle, CellRenderProps<Data, Value>
|
||||
implements DataViewCellLifeCycle, CellRenderProps<Data, RawValue, JsonValue>
|
||||
{
|
||||
get expose() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor cell!: Cell<Value, Data>;
|
||||
accessor cell!: Cell<RawValue, JsonValue, Data>;
|
||||
|
||||
readonly$ = computed(() => {
|
||||
return this.cell.property.readonly$.value;
|
||||
@@ -101,11 +102,11 @@ export abstract class BaseCellRenderer<
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
valueSetImmediate(value: Value | undefined): void {
|
||||
valueSetImmediate(value: RawValue | undefined): void {
|
||||
this.cell.valueSet(value);
|
||||
}
|
||||
|
||||
valueSetNextTick(value: Value | undefined) {
|
||||
valueSetNextTick(value: RawValue | undefined) {
|
||||
requestAnimationFrame(() => {
|
||||
this.cell.valueSet(value);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PropertyModel } from './property-config.js';
|
||||
import type {
|
||||
GetCellDataFromConfig,
|
||||
GetPropertyDataFromConfig,
|
||||
GetRawValueFromConfig,
|
||||
} from './types.js';
|
||||
|
||||
export type ConvertFunction<
|
||||
@@ -9,14 +9,14 @@ export type ConvertFunction<
|
||||
To extends PropertyModel = PropertyModel,
|
||||
> = (
|
||||
property: GetPropertyDataFromConfig<From['config']>,
|
||||
cells: (GetCellDataFromConfig<From['config']> | undefined)[]
|
||||
cells: (GetRawValueFromConfig<From['config']> | undefined)[]
|
||||
) => {
|
||||
property: GetPropertyDataFromConfig<To['config']>;
|
||||
cells: (GetCellDataFromConfig<To['config']> | undefined)[];
|
||||
cells: (GetRawValueFromConfig<To['config']> | undefined)[];
|
||||
};
|
||||
export const createPropertyConvert = <
|
||||
From extends PropertyModel<any, any, any>,
|
||||
To extends PropertyModel<any, any, any>,
|
||||
From extends PropertyModel<any, any, any, any>,
|
||||
To extends PropertyModel<any, any, any, any>,
|
||||
>(
|
||||
from: From,
|
||||
to: To,
|
||||
|
||||
@@ -5,9 +5,10 @@ import type { Cell } from '../view-manager/cell.js';
|
||||
|
||||
export interface CellRenderProps<
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> {
|
||||
cell: Cell<Value, Data>;
|
||||
cell: Cell<RawValue, JsonValue, Data>;
|
||||
isEditing$: ReadonlySignal<boolean>;
|
||||
selectCurrentCell: (editing: boolean) => void;
|
||||
}
|
||||
@@ -27,12 +28,17 @@ export interface DataViewCellLifeCycle {
|
||||
|
||||
export type DataViewCellComponent<
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
Value = unknown,
|
||||
> = UniComponent<CellRenderProps<Data, Value>, DataViewCellLifeCycle>;
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> = UniComponent<
|
||||
CellRenderProps<Data, RawValue, JsonValue>,
|
||||
DataViewCellLifeCycle
|
||||
>;
|
||||
|
||||
export type CellRenderer<
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> = {
|
||||
view: DataViewCellComponent<Data, Value>;
|
||||
view: DataViewCellComponent<Data, RawValue, JsonValue>;
|
||||
};
|
||||
|
||||
@@ -4,20 +4,22 @@ import type { PropertyConfig } from './types.js';
|
||||
export type PropertyMetaConfig<
|
||||
Type extends string = string,
|
||||
PropertyData extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
CellData = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> = {
|
||||
type: Type;
|
||||
config: PropertyConfig<PropertyData, CellData>;
|
||||
config: PropertyConfig<PropertyData, RawValue, JsonValue>;
|
||||
create: Create<PropertyData>;
|
||||
renderer: Renderer<PropertyData, CellData>;
|
||||
renderer: Renderer<PropertyData, RawValue, JsonValue>;
|
||||
};
|
||||
type CreatePropertyMeta<
|
||||
Type extends string = string,
|
||||
PropertyData extends Record<string, unknown> = Record<string, never>,
|
||||
CellData = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> = (
|
||||
renderer: Omit<Renderer<PropertyData, CellData>, 'type'>
|
||||
) => PropertyMetaConfig<Type, PropertyData, CellData>;
|
||||
renderer: Omit<Renderer<PropertyData, RawValue, JsonValue>, 'type'>
|
||||
) => PropertyMetaConfig<Type, PropertyData, RawValue, JsonValue>;
|
||||
type Create<
|
||||
PropertyData extends Record<string, unknown> = Record<string, never>,
|
||||
> = (
|
||||
@@ -32,26 +34,33 @@ type Create<
|
||||
export type PropertyModel<
|
||||
Type extends string = string,
|
||||
PropertyData extends Record<string, unknown> = Record<string, unknown>,
|
||||
CellData = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> = {
|
||||
type: Type;
|
||||
config: PropertyConfig<PropertyData, CellData>;
|
||||
config: PropertyConfig<PropertyData, RawValue, JsonValue>;
|
||||
create: Create<PropertyData>;
|
||||
createPropertyMeta: CreatePropertyMeta<Type, PropertyData, CellData>;
|
||||
createPropertyMeta: CreatePropertyMeta<
|
||||
Type,
|
||||
PropertyData,
|
||||
RawValue,
|
||||
JsonValue
|
||||
>;
|
||||
};
|
||||
export const propertyType = <Type extends string>(type: Type) => ({
|
||||
type: type,
|
||||
modelConfig: <
|
||||
CellData,
|
||||
PropertyData extends Record<string, unknown> = Record<string, never>,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
>(
|
||||
ops: PropertyConfig<PropertyData, CellData>
|
||||
): PropertyModel<Type, PropertyData, CellData> => {
|
||||
ops: PropertyConfig<PropertyData, RawValue, JsonValue>
|
||||
): PropertyModel<Type, PropertyData, RawValue, JsonValue> => {
|
||||
const create: Create<PropertyData> = (name, data) => {
|
||||
return {
|
||||
type,
|
||||
name,
|
||||
data: data ?? ops.defaultData(),
|
||||
data: data ?? ops.propertyData.default(),
|
||||
};
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -6,18 +6,20 @@ import type { CellRenderer, DataViewCellComponent } from './manager.js';
|
||||
|
||||
export interface Renderer<
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
> {
|
||||
type: string;
|
||||
icon?: UniComponent;
|
||||
cellRenderer: CellRenderer<Data, Value>;
|
||||
cellRenderer: CellRenderer<Data, RawValue, JsonValue>;
|
||||
}
|
||||
|
||||
export const createFromBaseCellRenderer = <
|
||||
Value,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>,
|
||||
>(
|
||||
renderer: new () => BaseCellRenderer<Value, Data>
|
||||
renderer: new () => BaseCellRenderer<RawValue, JsonValue, Data>
|
||||
): DataViewCellComponent => {
|
||||
return createUniComponentFromWebComponent(renderer as never) as never;
|
||||
};
|
||||
|
||||
@@ -8,86 +8,84 @@ export type WithCommonPropertyConfig<T = {}> = T & {
|
||||
dataSource: DataSource;
|
||||
};
|
||||
export type GetPropertyDataFromConfig<T> =
|
||||
T extends PropertyConfig<infer R, any> ? R : never;
|
||||
export type GetCellDataFromConfig<T> =
|
||||
T extends PropertyConfig<any, infer R> ? R : never;
|
||||
export type PropertyConfig<
|
||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||
Value = unknown,
|
||||
> = {
|
||||
T extends PropertyConfig<infer R, any, any> ? R : never;
|
||||
export type GetRawValueFromConfig<T> =
|
||||
T extends PropertyConfig<any, infer R, any> ? R : never;
|
||||
export type GetJsonValueFromConfig<T> =
|
||||
T extends PropertyConfig<any, any, infer R> ? R : never;
|
||||
export type PropertyConfig<Data, RawValue = unknown, JsonValue = unknown> = {
|
||||
name: string;
|
||||
valueSchema: ZodType<Value>;
|
||||
hide?: boolean;
|
||||
propertyData: {
|
||||
schema: ZodType<Data>;
|
||||
default: () => Data;
|
||||
};
|
||||
rawValue: {
|
||||
schema: ZodType<RawValue>;
|
||||
default: () => RawValue;
|
||||
toString: (config: { value: RawValue; data: Data }) => string;
|
||||
fromString: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: string;
|
||||
data: Data;
|
||||
}>
|
||||
) => {
|
||||
value: unknown;
|
||||
data?: Record<string, unknown>;
|
||||
};
|
||||
toJson: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: RawValue;
|
||||
data: Data;
|
||||
}>
|
||||
) => JsonValue;
|
||||
fromJson: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: JsonValue;
|
||||
data: Data;
|
||||
}>
|
||||
) => RawValue | undefined;
|
||||
setValue?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
data: Data;
|
||||
value: RawValue;
|
||||
newValue: RawValue;
|
||||
setValue: (value: RawValue) => void;
|
||||
}>
|
||||
) => void;
|
||||
onUpdate?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: RawValue;
|
||||
data: Data;
|
||||
callback: () => void;
|
||||
}>
|
||||
) => Disposable;
|
||||
};
|
||||
jsonValue: {
|
||||
schema: ZodType<JsonValue>;
|
||||
type: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
data: Data;
|
||||
}>
|
||||
) => TypeInstance;
|
||||
isEmpty: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: JsonValue;
|
||||
}>
|
||||
) => boolean;
|
||||
};
|
||||
fixed?: {
|
||||
defaultData: Data;
|
||||
defaultOrder?: string;
|
||||
defaultShow?: boolean;
|
||||
};
|
||||
defaultData: () => Data;
|
||||
type: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
data: Data;
|
||||
}>
|
||||
) => TypeInstance;
|
||||
formatValue?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: Value;
|
||||
data: Data;
|
||||
}>
|
||||
) => Value;
|
||||
isEmpty: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value?: Value;
|
||||
}>
|
||||
) => boolean;
|
||||
minWidth?: number;
|
||||
values?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value?: Value;
|
||||
}>
|
||||
) => unknown[];
|
||||
cellToString: (config: { value: Value; data: Data }) => string;
|
||||
cellFromString: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: string;
|
||||
data: Data;
|
||||
}>
|
||||
) => {
|
||||
value: unknown;
|
||||
data?: Record<string, unknown>;
|
||||
};
|
||||
cellToJson: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value?: Value;
|
||||
data: Data;
|
||||
}>
|
||||
) => DVJSON;
|
||||
cellFromJson: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: DVJSON;
|
||||
data: Data;
|
||||
}>
|
||||
) => Value | undefined;
|
||||
addGroup?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
text: string;
|
||||
oldData: Data;
|
||||
}>
|
||||
) => Data;
|
||||
onUpdate?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: Value;
|
||||
data: Data;
|
||||
callback: () => void;
|
||||
}>
|
||||
) => Disposable;
|
||||
valueUpdate?: (
|
||||
config: WithCommonPropertyConfig<{
|
||||
value: Value;
|
||||
data: Data;
|
||||
newValue: Value;
|
||||
}>
|
||||
) => Value;
|
||||
};
|
||||
|
||||
export type DVJSON =
|
||||
|
||||
30
blocksuite/affine/data-view/src/core/property/utils.ts
Normal file
30
blocksuite/affine/data-view/src/core/property/utils.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { DataSource } from '../data-source/base';
|
||||
import type { PropertyConfig } from './types';
|
||||
|
||||
export const fromJson = <Data, RawValue, JsonValue>(
|
||||
config: PropertyConfig<Data, RawValue, JsonValue>,
|
||||
{
|
||||
value,
|
||||
data,
|
||||
dataSource,
|
||||
}: {
|
||||
value: unknown;
|
||||
data: Data;
|
||||
dataSource: DataSource;
|
||||
}
|
||||
): RawValue | undefined => {
|
||||
const fromJson = config.rawValue.fromJson;
|
||||
const jsonSchema = config.jsonValue.schema;
|
||||
if (!fromJson || !jsonSchema) {
|
||||
return;
|
||||
}
|
||||
const jsonResult = jsonSchema.safeParse(value);
|
||||
if (!jsonResult.success) {
|
||||
return;
|
||||
}
|
||||
return fromJson({
|
||||
value: jsonResult.data,
|
||||
data,
|
||||
dataSource,
|
||||
});
|
||||
};
|
||||
@@ -19,16 +19,14 @@ export const anyTypeStatsFunctions: StatisticsConfig[] = [
|
||||
displayName: 'Values',
|
||||
type: 'count-values',
|
||||
dataType: t.unknown.instance(),
|
||||
impl: (data, { meta, dataSource }) => {
|
||||
const values = data
|
||||
.flatMap(v => {
|
||||
if (meta.config.values) {
|
||||
return meta.config.values({ value: v, dataSource });
|
||||
}
|
||||
return v;
|
||||
})
|
||||
.filter(v => v != null);
|
||||
return values.length.toString();
|
||||
impl: data => {
|
||||
const values = data.reduce((acc: number, v) => {
|
||||
if (Array.isArray(v)) {
|
||||
return acc + v.length;
|
||||
}
|
||||
return acc + (v == null ? 0 : 1);
|
||||
}, 0);
|
||||
return values.toString();
|
||||
},
|
||||
}),
|
||||
createStatisticConfig({
|
||||
@@ -37,13 +35,13 @@ export const anyTypeStatsFunctions: StatisticsConfig[] = [
|
||||
displayName: 'Unique Values',
|
||||
type: 'count-unique-values',
|
||||
dataType: t.unknown.instance(),
|
||||
impl: (data, { meta, dataSource }) => {
|
||||
impl: data => {
|
||||
const values = data
|
||||
.flatMap(v => {
|
||||
if (meta.config.values) {
|
||||
return meta.config.values({ value: v, dataSource });
|
||||
if (Array.isArray(v)) {
|
||||
return v;
|
||||
}
|
||||
return v;
|
||||
return [v];
|
||||
})
|
||||
.filter(v => v != null);
|
||||
return new Set(values).size.toString();
|
||||
@@ -57,7 +55,7 @@ export const anyTypeStatsFunctions: StatisticsConfig[] = [
|
||||
dataType: t.unknown.instance(),
|
||||
impl: (data, { meta, dataSource }) => {
|
||||
const emptyList = data.filter(value =>
|
||||
meta.config.isEmpty({ value, dataSource })
|
||||
meta.config.jsonValue.isEmpty({ value, dataSource })
|
||||
);
|
||||
return emptyList.length.toString();
|
||||
},
|
||||
@@ -70,7 +68,7 @@ export const anyTypeStatsFunctions: StatisticsConfig[] = [
|
||||
dataType: t.unknown.instance(),
|
||||
impl: (data, { meta, dataSource }) => {
|
||||
const notEmptyList = data.filter(
|
||||
value => !meta.config.isEmpty({ value, dataSource })
|
||||
value => !meta.config.jsonValue.isEmpty({ value, dataSource })
|
||||
);
|
||||
return notEmptyList.length.toString();
|
||||
},
|
||||
@@ -84,7 +82,7 @@ export const anyTypeStatsFunctions: StatisticsConfig[] = [
|
||||
impl: (data, { meta, dataSource }) => {
|
||||
if (data.length === 0) return '';
|
||||
const emptyList = data.filter(value =>
|
||||
meta.config.isEmpty({ value, dataSource })
|
||||
meta.config.jsonValue.isEmpty({ value, dataSource })
|
||||
);
|
||||
return ((emptyList.length / data.length) * 100).toFixed(2) + '%';
|
||||
},
|
||||
@@ -98,7 +96,7 @@ export const anyTypeStatsFunctions: StatisticsConfig[] = [
|
||||
impl: (data, { meta, dataSource }) => {
|
||||
if (data.length === 0) return '';
|
||||
const notEmptyList = data.filter(
|
||||
value => !meta.config.isEmpty({ value, dataSource })
|
||||
value => !meta.config.jsonValue.isEmpty({ value, dataSource })
|
||||
);
|
||||
return ((notEmptyList.length / data.length) * 100).toFixed(2) + '%';
|
||||
},
|
||||
|
||||
@@ -5,26 +5,29 @@ import type { Row } from './row.js';
|
||||
import type { SingleView } from './single-view.js';
|
||||
|
||||
export interface Cell<
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
readonly rowId: string;
|
||||
readonly view: SingleView;
|
||||
readonly row: Row;
|
||||
readonly propertyId: string;
|
||||
readonly property: Property<Value, Data>;
|
||||
readonly property: Property<RawValue, JsonValue, Data>;
|
||||
readonly isEmpty$: ReadonlySignal<boolean>;
|
||||
readonly stringValue$: ReadonlySignal<string>;
|
||||
readonly jsonValue$: ReadonlySignal<unknown>;
|
||||
readonly jsonValue$: ReadonlySignal<JsonValue>;
|
||||
|
||||
readonly value$: ReadonlySignal<Value | undefined>;
|
||||
valueSet(value: Value | undefined): void;
|
||||
readonly value$: ReadonlySignal<RawValue | undefined>;
|
||||
|
||||
valueSet(value: RawValue | undefined): void;
|
||||
}
|
||||
|
||||
export class CellBase<
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>,
|
||||
> implements Cell<Value, Data>
|
||||
> implements Cell<RawValue, JsonValue, Data>
|
||||
{
|
||||
meta$ = computed(() => {
|
||||
return this.view.manager.dataSource.propertyMetaGet(
|
||||
@@ -36,29 +39,35 @@ export class CellBase<
|
||||
return this.view.manager.dataSource.cellValueGet(
|
||||
this.rowId,
|
||||
this.propertyId
|
||||
) as Value;
|
||||
) as RawValue;
|
||||
});
|
||||
|
||||
isEmpty$: ReadonlySignal<boolean> = computed(() => {
|
||||
return this.meta$.value.config.isEmpty({
|
||||
value: this.value$.value,
|
||||
dataSource: this.view.manager.dataSource,
|
||||
});
|
||||
return (
|
||||
this.meta$.value?.config.jsonValue.isEmpty({
|
||||
value: this.jsonValue$.value,
|
||||
dataSource: this.view.manager.dataSource,
|
||||
}) ?? true
|
||||
);
|
||||
});
|
||||
|
||||
jsonValue$: ReadonlySignal<unknown> = computed(() => {
|
||||
return this.view.cellJsonValueGet(this.rowId, this.propertyId);
|
||||
jsonValue$: ReadonlySignal<JsonValue> = computed(() => {
|
||||
return this.view.cellJsonValueGet(this.rowId, this.propertyId) as JsonValue;
|
||||
});
|
||||
|
||||
property$ = computed(() => {
|
||||
return this.view.propertyGet(this.propertyId) as Property<Value, Data>;
|
||||
return this.view.propertyGet(this.propertyId) as Property<
|
||||
RawValue,
|
||||
JsonValue,
|
||||
Data
|
||||
>;
|
||||
});
|
||||
|
||||
stringValue$: ReadonlySignal<string> = computed(() => {
|
||||
return this.view.cellStringValueGet(this.rowId, this.propertyId)!;
|
||||
});
|
||||
|
||||
get property(): Property<Value, Data> {
|
||||
get property(): Property<RawValue, JsonValue, Data> {
|
||||
return this.property$.value;
|
||||
}
|
||||
|
||||
@@ -72,7 +81,7 @@ export class CellBase<
|
||||
public rowId: string
|
||||
) {}
|
||||
|
||||
valueSet(value: unknown | undefined): void {
|
||||
valueSet(value: RawValue | undefined): void {
|
||||
this.view.manager.dataSource.cellValueChange(
|
||||
this.rowId,
|
||||
this.propertyId,
|
||||
|
||||
@@ -8,7 +8,8 @@ import type { Cell } from './cell.js';
|
||||
import type { SingleView } from './single-view.js';
|
||||
|
||||
export interface Property<
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
readonly id: string;
|
||||
@@ -28,7 +29,7 @@ export interface Property<
|
||||
readonly duplicate?: () => void;
|
||||
get canDuplicate(): boolean;
|
||||
|
||||
cellGet(rowId: string): Cell<Value>;
|
||||
cellGet(rowId: string): Cell<RawValue, JsonValue, Data>;
|
||||
|
||||
readonly data$: ReadonlySignal<Data>;
|
||||
dataUpdate(updater: PropertyDataUpdater<Data>): void;
|
||||
@@ -44,17 +45,18 @@ export interface Property<
|
||||
hideSet(hide: boolean): void;
|
||||
get hideCanSet(): boolean;
|
||||
|
||||
valueGet(rowId: string): Value | undefined;
|
||||
valueSet(rowId: string, value: Value | undefined): void;
|
||||
valueGet(rowId: string): RawValue | undefined;
|
||||
valueSet(rowId: string, value: RawValue | undefined): void;
|
||||
|
||||
stringValueGet(rowId: string): string;
|
||||
valueSetFromString(rowId: string, value: string): void;
|
||||
}
|
||||
|
||||
export abstract class PropertyBase<
|
||||
Value = unknown,
|
||||
RawValue = unknown,
|
||||
JsonValue = unknown,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>,
|
||||
> implements Property<Value, Data>
|
||||
> implements Property<RawValue, JsonValue, Data>
|
||||
{
|
||||
cells$ = computed(() => {
|
||||
return this.view.rows$.value.map(id => this.cellGet(id));
|
||||
@@ -141,8 +143,8 @@ export abstract class PropertyBase<
|
||||
return this.view.propertyCanHide(this.id);
|
||||
}
|
||||
|
||||
cellGet(rowId: string): Cell<Value> {
|
||||
return this.view.cellGet(rowId, this.id) as Cell<Value>;
|
||||
cellGet(rowId: string): Cell<RawValue, JsonValue, Data> {
|
||||
return this.view.cellGet(rowId, this.id) as Cell<RawValue, JsonValue, Data>;
|
||||
}
|
||||
|
||||
dataUpdate(updater: PropertyDataUpdater<Data>): void {
|
||||
@@ -165,11 +167,11 @@ export abstract class PropertyBase<
|
||||
return this.cellGet(rowId).stringValue$.value;
|
||||
}
|
||||
|
||||
valueGet(rowId: string): Value | undefined {
|
||||
valueGet(rowId: string): RawValue | undefined {
|
||||
return this.cellGet(rowId).value$.value;
|
||||
}
|
||||
|
||||
valueSet(rowId: string, value: Value | undefined): void {
|
||||
valueSet(rowId: string, value: RawValue | undefined): void {
|
||||
return this.cellGet(rowId).valueSet(value);
|
||||
}
|
||||
|
||||
@@ -181,6 +183,6 @@ export abstract class PropertyBase<
|
||||
if (data.data) {
|
||||
this.dataUpdate(() => data.data as Data);
|
||||
}
|
||||
this.valueSet(rowId, data.value as Value);
|
||||
this.valueSet(rowId, data.value as RawValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ 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 { DVJSON } from '../index.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 type { DataViewDataType, ViewMeta } from '../view/data-view.js';
|
||||
@@ -50,9 +50,9 @@ export interface SingleView {
|
||||
|
||||
cellValueSet(rowId: string, propertyId: string, value: unknown): void;
|
||||
|
||||
cellJsonValueGet(rowId: string, propertyId: string): DVJSON;
|
||||
cellJsonValueGet(rowId: string, propertyId: string): unknown | null;
|
||||
|
||||
cellJsonValueSet(rowId: string, propertyId: string, value: DVJSON): void;
|
||||
cellJsonValueSet(rowId: string, propertyId: string, value: unknown): void;
|
||||
|
||||
cellStringValueGet(rowId: string, propertyId: string): string | undefined;
|
||||
|
||||
@@ -82,12 +82,17 @@ export interface SingleView {
|
||||
|
||||
readonly propertyMetas$: ReadonlySignal<PropertyMetaConfig[]>;
|
||||
|
||||
propertyAdd(toAfterOfProperty: InsertToPosition, type?: string): string;
|
||||
propertyAdd(
|
||||
toAfterOfProperty: InsertToPosition,
|
||||
type?: string
|
||||
): string | undefined;
|
||||
|
||||
propertyDelete(propertyId: string): void;
|
||||
|
||||
propertyCanDelete(propertyId: string): boolean;
|
||||
|
||||
propertyDuplicate(propertyId: string): void;
|
||||
|
||||
propertyCanDuplicate(propertyId: string): boolean;
|
||||
|
||||
propertyGet(propertyId: string): Property;
|
||||
@@ -105,11 +110,13 @@ export interface SingleView {
|
||||
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>;
|
||||
@@ -187,13 +194,16 @@ export abstract class SingleViewBase<
|
||||
});
|
||||
|
||||
vars$ = computed(() => {
|
||||
return this.propertiesWithoutFilter$.value.map(id => {
|
||||
return this.propertiesWithoutFilter$.value.flatMap(id => {
|
||||
const v = this.propertyGet(id);
|
||||
const propertyMeta = this.dataSource.propertyMetaGet(v.type$.value);
|
||||
if (!propertyMeta) {
|
||||
return [];
|
||||
}
|
||||
return {
|
||||
id: v.id,
|
||||
name: v.name$.value,
|
||||
type: propertyMeta.config.type({
|
||||
type: propertyMeta.config.jsonValue.type({
|
||||
data: v.data$.value,
|
||||
dataSource: this.dataSource,
|
||||
}),
|
||||
@@ -229,15 +239,19 @@ export abstract class SingleViewBase<
|
||||
public manager: ViewManager,
|
||||
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';
|
||||
}
|
||||
@@ -264,33 +278,35 @@ export abstract class SingleViewBase<
|
||||
return new CellBase(this, propertyId, rowId);
|
||||
}
|
||||
|
||||
cellJsonValueGet(rowId: string, propertyId: string): DVJSON {
|
||||
cellJsonValueGet(rowId: string, propertyId: string): unknown | null {
|
||||
const type = this.propertyTypeGet(propertyId);
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
return this.dataSource.propertyMetaGet(type).config.cellToJson({
|
||||
value: this.dataSource.cellValueGet(rowId, propertyId),
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this.dataSource,
|
||||
});
|
||||
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: DVJSON): void {
|
||||
cellJsonValueSet(rowId: string, propertyId: string, value: unknown): void {
|
||||
const type = this.propertyTypeGet(propertyId);
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
const fromJson = this.dataSource.propertyMetaGet(type).config.cellFromJson;
|
||||
this.dataSource.cellValueChange(
|
||||
rowId,
|
||||
propertyId,
|
||||
fromJson({
|
||||
value,
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this.dataSource,
|
||||
})
|
||||
);
|
||||
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 {
|
||||
@@ -299,7 +315,7 @@ export abstract class SingleViewBase<
|
||||
return;
|
||||
}
|
||||
return (
|
||||
this.dataSource.propertyMetaGet(type).config.cellToString({
|
||||
this.dataSource.propertyMetaGet(type)?.config.rawValue.toString({
|
||||
value: this.dataSource.cellValueGet(rowId, propertyId),
|
||||
data: this.propertyDataGet(propertyId),
|
||||
}) ?? ''
|
||||
@@ -311,14 +327,7 @@ export abstract class SingleViewBase<
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
const cellValue = this.dataSource.cellValueGet(rowId, propertyId);
|
||||
return (
|
||||
this.dataSource.propertyMetaGet(type).config.formatValue?.({
|
||||
value: cellValue,
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this.dataSource,
|
||||
}) ?? cellValue
|
||||
);
|
||||
return this.dataSource.cellValueGet(rowId, propertyId);
|
||||
}
|
||||
|
||||
cellValueSet(rowId: string, propertyId: string, value: unknown): void {
|
||||
@@ -355,8 +364,11 @@ export abstract class SingleViewBase<
|
||||
});
|
||||
}
|
||||
|
||||
propertyAdd(position: InsertToPosition, type?: string): string {
|
||||
propertyAdd(position: InsertToPosition, type?: string): string | undefined {
|
||||
const id = this.dataSource.propertyAdd(position, type);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
this.propertyMove(id, position);
|
||||
return id;
|
||||
}
|
||||
@@ -374,7 +386,11 @@ export abstract class SingleViewBase<
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
return this.dataSource.propertyMetaGet(type).config.type({
|
||||
const meta = this.dataSource.propertyMetaGet(type);
|
||||
if (!meta) {
|
||||
return;
|
||||
}
|
||||
return meta.config.jsonValue.type({
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this.dataSource,
|
||||
});
|
||||
@@ -402,7 +418,7 @@ export abstract class SingleViewBase<
|
||||
abstract propertyHideSet(propertyId: string, hide: boolean): void;
|
||||
|
||||
propertyIconGet(type: string): UniComponent | undefined {
|
||||
return this.dataSource.propertyMetaGet(type).renderer.icon;
|
||||
return this.dataSource.propertyMetaGet(type)?.renderer.icon;
|
||||
}
|
||||
|
||||
propertyIdGetByIndex(index: number): string | undefined {
|
||||
@@ -413,7 +429,7 @@ export abstract class SingleViewBase<
|
||||
return this.propertyIds$.value.indexOf(propertyId);
|
||||
}
|
||||
|
||||
propertyMetaGet(type: string): PropertyMetaConfig {
|
||||
propertyMetaGet(type: string): PropertyMetaConfig | undefined {
|
||||
return this.dataSource.propertyMetaGet(type);
|
||||
}
|
||||
|
||||
@@ -439,13 +455,16 @@ export abstract class SingleViewBase<
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
this.dataSource.propertyMetaGet(type).config.cellFromString({
|
||||
value: cellData,
|
||||
data: this.propertyDataGet(propertyId),
|
||||
dataSource: this.dataSource,
|
||||
}) ?? ''
|
||||
);
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user