diff --git a/packages/grid/src/types/index.ts b/packages/grid/src/types/index.ts index e4b678e4..c576beb6 100644 --- a/packages/grid/src/types/index.ts +++ b/packages/grid/src/types/index.ts @@ -6,3 +6,4 @@ export * from './field'; export * from './grid'; export * from './layout'; export * from './row'; +export * from './view'; diff --git a/packages/grid/src/types/view.ts b/packages/grid/src/types/view.ts new file mode 100644 index 00000000..05791c1c --- /dev/null +++ b/packages/grid/src/types/view.ts @@ -0,0 +1,24 @@ +import { Id } from 'ngx-tethys/types'; + +export enum AITableFilterOperation { + eq = 'eq', + gte = 'gte', + lte = 'lte', + gt = 'gt', + lt = 'lt', + in = 'in', + contain = 'contain', + ne = 'ne', + nin = 'nin', + between = 'between', + besides = 'besides', + empty = 'empty', + exists = 'exists', + notContain = 'not_contain' +} + +export interface AITableFilterCondition { + field_id: Id; + operation: AITableFilterOperation; + value: TValue; +} diff --git a/packages/grid/src/utils/field/field.ts b/packages/grid/src/utils/field/field.ts deleted file mode 100644 index 57c90dc0..00000000 --- a/packages/grid/src/utils/field/field.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { AITableField, AITableReferences } from '../../index'; - -export abstract class Field { - abstract cellFullText(transformValue: any, field: AITableField, references?: AITableReferences): string[]; -} diff --git a/packages/grid/src/utils/field/index.ts b/packages/grid/src/utils/field/index.ts deleted file mode 100644 index aaa4661a..00000000 --- a/packages/grid/src/utils/field/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AITableFieldType } from '../../index'; -import { Field } from './field'; -import { LinkField } from './link'; -import { MemberField } from './member'; -import { ProgressField } from './progress'; -import { SelectField } from './select'; -import { TextField } from './text'; - -export const ViewOperationMap: Record = { - [AITableFieldType.text]: new TextField(), - [AITableFieldType.richText]: new TextField(), - [AITableFieldType.select]: new SelectField(), - [AITableFieldType.date]: new TextField(), - [AITableFieldType.createdAt]: new TextField(), - [AITableFieldType.updatedAt]: new TextField(), - [AITableFieldType.number]: new TextField(), - [AITableFieldType.rate]: new TextField(), - [AITableFieldType.link]: new LinkField(), - [AITableFieldType.member]: new MemberField(), - [AITableFieldType.progress]: new ProgressField(), - [AITableFieldType.createdBy]: new MemberField(), - [AITableFieldType.updatedBy]: new MemberField() -}; diff --git a/packages/grid/src/utils/field/link.ts b/packages/grid/src/utils/field/link.ts deleted file mode 100644 index dfcd9a83..00000000 --- a/packages/grid/src/utils/field/link.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AITableField, LinkFieldValue } from '../../index'; -import { Field } from './field'; -import { isEmpty } from '../common'; - -export class LinkField extends Field { - cellFullText(transformValue: LinkFieldValue, field: AITableField): string[] { - let fullText: string[] = []; - if (!isEmpty(transformValue?.text)) { - fullText.push(transformValue.text); - } - return fullText; - } -} diff --git a/packages/grid/src/utils/field/member.ts b/packages/grid/src/utils/field/member.ts deleted file mode 100644 index 7d1119f4..00000000 --- a/packages/grid/src/utils/field/member.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AITableField, AITableReferences } from '../../index'; -import { Field } from './field'; - -export class MemberField extends Field { - cellFullText(transformValue: string[], field: AITableField, references?: AITableReferences): string[] { - let fullText: string[] = []; - if (transformValue?.length && references) { - for (let index = 0; index < transformValue.length; index++) { - const userInfo = references?.members[transformValue[index]]; - if (!userInfo) { - continue; - } - if (userInfo.display_name) { - fullText.push(userInfo.display_name); - } - } - } - return fullText; - } -} diff --git a/packages/state/src/utils/field/model/date.ts b/packages/grid/src/utils/field/model/date.ts similarity index 77% rename from packages/state/src/utils/field/model/date.ts rename to packages/grid/src/utils/field/model/date.ts index a8df434b..d45ac83e 100644 --- a/packages/state/src/utils/field/model/date.ts +++ b/packages/grid/src/utils/field/model/date.ts @@ -1,9 +1,9 @@ -import { Field } from './field'; -import { DateFieldValue } from '@ai-table/grid'; import { fromUnixTime, subDays } from 'date-fns'; -import { isArray, TinyDate } from 'ngx-tethys/util'; -import { AITableFilterCondition, AITableFilterOperation } from '../../../types' -import { isEmpty } from '../../common'; +import { isArray, isEmpty, TinyDate } from 'ngx-tethys/util'; +import { Field } from './field'; +import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; +import { DateFieldValue } from '../../../core'; +import { compareNumber } from '../operate'; export class DateField extends Field { override isMeetFilter(condition: AITableFilterCondition, cellValue: DateFieldValue) { @@ -26,6 +26,12 @@ export class DateField extends Field { } } + override compare(cellValue1: DateFieldValue, cellValue2: DateFieldValue): number { + const value1 = cellValueToSortValue(cellValue1); + const value2 = cellValueToSortValue(cellValue2); + return compareNumber(value1, value2); + } + getTimeRange(value: string | number | number[]) { switch (value) { case 'today': @@ -55,26 +61,8 @@ export class DateField extends Field { ]; } } +} - cellValueToString(_cellValue: DateFieldValue): string | null { - return null; - } - - static _compare(cellValue1: DateFieldValue, cellValue2: DateFieldValue): number { - if (isEmpty(cellValue1?.timestamp) && isEmpty(cellValue2?.timestamp)) { - return 0; - } - if (isEmpty(cellValue1?.timestamp)) { - return -1; - } - if (isEmpty(cellValue2?.timestamp)) { - return 1; - } - - return cellValue1.timestamp === cellValue2.timestamp ? 0 : cellValue1.timestamp > cellValue2.timestamp ? 1 : -1; - } - - override compare(cellValue1: DateFieldValue, cellValue2: DateFieldValue): number { - return DateField._compare(cellValue1, cellValue2); - } +function cellValueToSortValue(cellValue: DateFieldValue): number { + return cellValue?.timestamp; } diff --git a/packages/grid/src/utils/field/model/field.ts b/packages/grid/src/utils/field/model/field.ts new file mode 100644 index 00000000..bd9786de --- /dev/null +++ b/packages/grid/src/utils/field/model/field.ts @@ -0,0 +1,44 @@ +import { AITableFilterCondition, AITableFilterOperation, AITableReferences } from '../../../types'; +import { AITableField, FieldValue } from '../../../core'; +import { isEmpty } from '../../common'; + +export abstract class Field { + // 排序 + abstract compare(cellValue1: FieldValue, cellValue2: FieldValue, field: AITableField, references?: AITableReferences): number; + + // 筛选 + isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { + switch (condition.operation) { + case AITableFilterOperation.empty: + case AITableFilterOperation.exists: { + return this.isEmptyOrNot(condition.operation, cellValue); + } + default: { + return true; + } + } + } + + // 查找 + cellFullText(transformValue: any, field: AITableField, references?: AITableReferences): string[] { + let fullText: string[] = []; + if (!isEmpty(transformValue)) { + fullText.push(String(transformValue)); + } + return fullText; + } + + isEmptyOrNot(operation: AITableFilterOperation.empty | AITableFilterOperation.exists, cellValue: FieldValue) { + switch (operation) { + case AITableFilterOperation.empty: { + return isEmpty(cellValue); + } + case AITableFilterOperation.exists: { + return !isEmpty(cellValue); + } + default: { + throw new Error('compare operator type error'); + } + } + } +} diff --git a/packages/state/src/utils/field/model/index.ts b/packages/grid/src/utils/field/model/index.ts similarity index 86% rename from packages/state/src/utils/field/model/index.ts rename to packages/grid/src/utils/field/model/index.ts index d2f48f41..dd428301 100644 --- a/packages/state/src/utils/field/model/index.ts +++ b/packages/grid/src/utils/field/model/index.ts @@ -1,12 +1,14 @@ -import { AITableFieldType } from '@ai-table/grid'; -import { Field } from './field'; -import { TextField } from './text'; -import { SelectField } from './select'; +import { AITableFieldType } from '../../../core'; import { DateField } from './date'; -import { NumberField } from './number'; -import { RateField } from './rate'; +import { Field } from './field'; + import { LinkField } from './link'; import { MemberField } from './member'; +import { NumberField } from './number'; +import { ProgressField } from './progress'; +import { RateField } from './rate'; +import { SelectField } from './select'; +import { TextField } from './text'; export const ViewOperationMap: Record = { [AITableFieldType.text]: new TextField(), @@ -19,7 +21,7 @@ export const ViewOperationMap: Record = { [AITableFieldType.rate]: new RateField(), [AITableFieldType.link]: new LinkField(), [AITableFieldType.member]: new MemberField(), - [AITableFieldType.progress]: new NumberField(), + [AITableFieldType.progress]: new ProgressField(), [AITableFieldType.createdBy]: new MemberField(), [AITableFieldType.updatedBy]: new MemberField() }; diff --git a/packages/grid/src/utils/field/model/link.ts b/packages/grid/src/utils/field/model/link.ts new file mode 100644 index 00000000..a539b457 --- /dev/null +++ b/packages/grid/src/utils/field/model/link.ts @@ -0,0 +1,37 @@ +import { FieldValue, LinkFieldValue } from '../../../core'; +import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; +import { isEmpty } from '../../common'; +import { compareString, stringInclude } from '../operate'; +import { Field } from './field'; + +export class LinkField extends Field { + override isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { + const cellTextValue = cellValue?.text; + switch (condition.operation) { + case AITableFilterOperation.empty: + return isEmpty(cellTextValue); + case AITableFilterOperation.exists: + return !isEmpty(cellTextValue); + case AITableFilterOperation.contain: + return !isEmpty(cellTextValue) && stringInclude(cellTextValue, condition.value); + default: + return super.isMeetFilter(condition, cellTextValue); + } + } + + override compare(cellValue1: FieldValue, cellValue2: FieldValue): number { + return compareString(cellValueToSortValue(cellValue1), cellValueToSortValue(cellValue2)); + } + + override cellFullText(transformValue: LinkFieldValue): string[] { + let texts: string[] = []; + if (!isEmpty(transformValue?.text)) { + texts.push(transformValue.text); + } + return texts; + } +} + +function cellValueToSortValue(cellValue: LinkFieldValue): string | null { + return (cellValue && cellValue.text && cellValue.text.trim()) || null; +} diff --git a/packages/grid/src/utils/field/model/member.ts b/packages/grid/src/utils/field/model/member.ts new file mode 100644 index 00000000..937a744d --- /dev/null +++ b/packages/grid/src/utils/field/model/member.ts @@ -0,0 +1,60 @@ +import { AITableField, FieldValue, MemberFieldValue } from '../../../core'; +import { AITableFilterCondition, AITableFilterOperation, AITableReferences } from '../../../types'; +import { isEmpty } from '../../common'; +import { compareString, hasIntersect } from '../operate'; +import { Field } from './field'; + +export class MemberField extends Field { + override isMeetFilter(condition: AITableFilterCondition, cellValue: MemberFieldValue) { + switch (condition.operation) { + case AITableFilterOperation.empty: + return isEmpty(cellValue); + case AITableFilterOperation.exists: + return !isEmpty(cellValue); + case AITableFilterOperation.in: + return Array.isArray(condition.value) && hasIntersect(cellValue, condition.value); + case AITableFilterOperation.nin: + return Array.isArray(condition.value) && !hasIntersect(cellValue, condition.value); + default: + return super.isMeetFilter(condition, cellValue); + } + } + + override compare(cellValue1: FieldValue, cellValue2: FieldValue, field: AITableField, references: AITableReferences): number { + const value1 = cellValueToSortValue(cellValue1, field, references); + const value2 = cellValueToSortValue(cellValue2, field, references); + return compareString(value1, value2); + } + + override cellFullText(transformValue: string[], field: AITableField, references?: AITableReferences): string[] { + let fullText: string[] = []; + if (transformValue?.length && references) { + for (let index = 0; index < transformValue.length; index++) { + const userInfo = references?.members[transformValue[index]]; + if (!userInfo) { + continue; + } + if (userInfo.display_name) { + fullText.push(userInfo.display_name); + } + } + } + return fullText; + } +} + +function cellValueToSortValue(cellValue: MemberFieldValue, field: AITableField, references: AITableReferences): string | null { + let names: string[] = []; + if (cellValue?.length && references) { + for (let index = 0; index < cellValue.length; index++) { + const userInfo = references?.members[cellValue[index]]; + if (!userInfo) { + continue; + } + if (userInfo.display_name_pinyin) { + names.push(userInfo.display_name_pinyin); + } + } + } + return names && names.length ? names.join(', ') : null; +} diff --git a/packages/state/src/utils/field/model/number.ts b/packages/grid/src/utils/field/model/number.ts similarity index 73% rename from packages/state/src/utils/field/model/number.ts rename to packages/grid/src/utils/field/model/number.ts index 893144b4..e5224d7f 100644 --- a/packages/state/src/utils/field/model/number.ts +++ b/packages/grid/src/utils/field/model/number.ts @@ -1,7 +1,8 @@ +import { FieldValue } from '../../../core'; import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; -import { Field } from './field'; -import { FieldValue } from '@ai-table/grid'; import { isEmpty } from '../../common'; +import { compareNumber } from '../operate'; +import { Field } from './field'; export class NumberField extends Field { override isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { @@ -27,24 +28,7 @@ export class NumberField extends Field { } } - cellValueToString(_cellValue: FieldValue): string | null { - return null; - } - - static _compare(cellValue1: number, cellValue2: number): number { - if (isEmpty(cellValue1) && isEmpty(cellValue2)) { - return 0; - } - if (isEmpty(cellValue1)) { - return -1; - } - if (isEmpty(cellValue2)) { - return 1; - } - return cellValue1 === cellValue2 ? 0 : cellValue1 > cellValue2 ? 1 : -1; - } - override compare(cellValue1: number, cellValue2: number): number { - return NumberField._compare(cellValue1, cellValue2); + return compareNumber(cellValue1, cellValue2); } } diff --git a/packages/grid/src/utils/field/model/progress.ts b/packages/grid/src/utils/field/model/progress.ts new file mode 100644 index 00000000..18ee7713 --- /dev/null +++ b/packages/grid/src/utils/field/model/progress.ts @@ -0,0 +1,41 @@ +import { FieldValue } from '../../../core'; +import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; +import { compareNumber, isEmpty } from '../../index'; +import { Field } from './field'; + +export class ProgressField extends Field { + override isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { + switch (condition.operation) { + case AITableFilterOperation.empty: + return isEmpty(cellValue); + case AITableFilterOperation.exists: + return !isEmpty(cellValue); + case AITableFilterOperation.eq: + return !Number.isNaN(condition.value) && cellValue != null && cellValue !== '' && condition.value === cellValue; + case AITableFilterOperation.gte: + return cellValue != null && cellValue !== '' && cellValue >= condition.value; + case AITableFilterOperation.lte: + return cellValue != null && cellValue !== '' && cellValue <= condition.value; + case AITableFilterOperation.gt: + return cellValue != null && cellValue !== '' && cellValue > condition.value; + case AITableFilterOperation.lt: + return cellValue != null && cellValue !== '' && cellValue < condition.value; + case AITableFilterOperation.ne: + return cellValue == null || cellValue == '' || Number.isNaN(condition.value) || cellValue !== condition.value; + default: + return super.isMeetFilter(condition, cellValue); + } + } + + override compare(cellValue1: number, cellValue2: number): number { + return compareNumber(cellValue1, cellValue2); + } + + override cellFullText(transformValue: number): string[] { + let fullText: string[] = []; + if (!isEmpty(transformValue)) { + fullText.push(`${transformValue}%`); + } + return fullText; + } +} diff --git a/packages/state/src/utils/field/model/rate.ts b/packages/grid/src/utils/field/model/rate.ts similarity index 65% rename from packages/state/src/utils/field/model/rate.ts rename to packages/grid/src/utils/field/model/rate.ts index 7e226e16..ea489e26 100644 --- a/packages/state/src/utils/field/model/rate.ts +++ b/packages/grid/src/utils/field/model/rate.ts @@ -1,7 +1,8 @@ -import { isEmpty } from '../../common'; +import { RateFieldValue } from '../../../core'; import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; +import { isEmpty } from '../../common'; +import { compareNumber } from '../operate'; import { Field } from './field'; -import { RateFieldValue } from '@ai-table/grid'; export class RateField extends Field { override isMeetFilter(condition: AITableFilterCondition, cellValue: RateFieldValue | string) { @@ -21,24 +22,7 @@ export class RateField extends Field { } } - cellValueToString(_cellValue: RateFieldValue): string | null { - return null; - } - - static _compare(cellValue1: number, cellValue2: number): number { - if (isEmpty(cellValue1) && isEmpty(cellValue2)) { - return 0; - } - if (isEmpty(cellValue1)) { - return -1; - } - if (isEmpty(cellValue2)) { - return 1; - } - return cellValue1 === cellValue2 ? 0 : cellValue1 > cellValue2 ? 1 : -1; - } - override compare(cellValue1: number, cellValue2: number): number { - return RateField._compare(cellValue1, cellValue2); + return compareNumber(cellValue1, cellValue2); } } diff --git a/packages/grid/src/utils/field/model/select.ts b/packages/grid/src/utils/field/model/select.ts new file mode 100644 index 00000000..3713a495 --- /dev/null +++ b/packages/grid/src/utils/field/model/select.ts @@ -0,0 +1,60 @@ +import { helpers } from 'ngx-tethys/util'; +import { Field } from './field'; +import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; +import { AITableField, FieldValue, SelectFieldValue, SelectSettings } from '../../../core'; +import { isEmpty } from '../../common'; +import { compareString, hasIntersect } from '../operate'; + +export class SelectField extends Field { + override isMeetFilter(condition: AITableFilterCondition, cellValue: SelectFieldValue) { + switch (condition.operation) { + case AITableFilterOperation.empty: + return isEmpty(cellValue); + case AITableFilterOperation.exists: + return !isEmpty(cellValue); + case AITableFilterOperation.in: + return Array.isArray(condition.value) && hasIntersect(cellValue, condition.value); + case AITableFilterOperation.nin: + return Array.isArray(condition.value) && !hasIntersect(cellValue, condition.value); + default: + return super.isMeetFilter(condition, cellValue); + } + } + + override compare(cellValue1: FieldValue, cellValue2: FieldValue, field: AITableField): number { + const value1 = cellValueToSortValue(cellValue1, field); + const value2 = cellValueToSortValue(cellValue2, field); + return compareString(value1, value2); + } + + override cellFullText(transformValue: string[], field: AITableField): string[] { + let fullText: string[] = []; + const optionsMap = helpers.keyBy((field.settings as SelectSettings).options || [], '_id'); + if (transformValue && Array.isArray(transformValue) && transformValue.length) { + transformValue.forEach((optionId) => { + const option = optionsMap[optionId]; + if (option && option.text) { + fullText.push(option.text); + } + }); + } + return fullText; + } +} + +function cellValueToSortValue(cellValue: SelectFieldValue, field: AITableField): string | null { + if (!cellValue) { + return null; + } + const texts: string[] = []; + const optionsMap = helpers.keyBy((field.settings as SelectSettings).options || [], '_id'); + if (cellValue && Array.isArray(cellValue) && cellValue.length) { + cellValue.forEach((optionId) => { + const option = optionsMap[optionId]; + if (option && option.text) { + texts.push(option.text); + } + }); + } + return texts && texts.length ? texts.join(',') : null; +} diff --git a/packages/state/src/utils/field/model/text.ts b/packages/grid/src/utils/field/model/text.ts similarity index 54% rename from packages/state/src/utils/field/model/text.ts rename to packages/grid/src/utils/field/model/text.ts index 13dc4dc9..c70bcb42 100644 --- a/packages/state/src/utils/field/model/text.ts +++ b/packages/grid/src/utils/field/model/text.ts @@ -1,7 +1,8 @@ -import { isEmpty } from '../../common'; +import { FieldValue } from '../../../core'; import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; +import { isEmpty } from '../../common'; +import { compareString, stringInclude } from '../operate'; import { Field } from './field'; -import { FieldValue } from '@ai-table/grid'; export class TextField extends Field { override isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { @@ -11,21 +12,19 @@ export class TextField extends Field { case AITableFilterOperation.exists: return !isEmpty(cellValue); case AITableFilterOperation.contain: - return !isEmpty(cellValue) && this.stringInclude(cellValue, condition.value); + return !isEmpty(cellValue) && stringInclude(cellValue, condition.value); default: return super.isMeetFilter(condition, cellValue); } } - static stringInclude(str: string, searchStr: string) { - return str.toLowerCase().includes(searchStr.trim().toLowerCase()); - } - - override eq(cv1: string | null, cv2: string | null): boolean { - return this.cellValueToString(cv1) === this.cellValueToString(cv2); + override compare(cellValue1: FieldValue, cellValue2: FieldValue): number { + const value1 = cellValueToSortValue(cellValue1); + const value2 = cellValueToSortValue(cellValue2); + return compareString(value1, value2); } +} - cellValueToString(cellValue: string | null): string | null { - return cellValue; - } +function cellValueToSortValue(cellValue: FieldValue): string | null { + return (cellValue && cellValue.trim()) || null; } diff --git a/packages/grid/src/utils/field/operate.ts b/packages/grid/src/utils/field/operate.ts new file mode 100644 index 00000000..ad3d7be4 --- /dev/null +++ b/packages/grid/src/utils/field/operate.ts @@ -0,0 +1,52 @@ +import { isEmpty } from '../common'; + +export const zhIntlCollator = typeof Intl !== 'undefined' ? new Intl.Collator('zh-CN') : undefined; + +export function compareNumber(a: number, b: number): number { + if (isEmpty(a) && isEmpty(b)) { + return 0; + } + if (isEmpty(a)) { + return -1; + } + if (isEmpty(b)) { + return 1; + } + return a === b ? 0 : a > b ? 1 : -1; +} + +export function compareString(a: string | null, b: string | null): number { + if (a === b) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + + // pinyin sort + return a === b ? 0 : zhIntlCollator ? zhIntlCollator.compare(a, b) : a.localeCompare(b, 'zh-CN') > 0 ? 1 : -1; +} + +export function stringInclude(str: string, searchStr: string) { + return str.toLowerCase().includes(searchStr.trim().toLowerCase()); +} + +/** + * 两数组是否有交集 + */ +export function hasIntersect(array1: T[], array2: T[]) { + if (!Array.isArray(array1) || !Array.isArray(array2)) { + return false; + } + const set1 = new Set(array1); + const set2 = new Set(array2); + for (const element of set1) { + if (set2.has(element)) { + return true; + } + } + return false; +} diff --git a/packages/grid/src/utils/field/progress.ts b/packages/grid/src/utils/field/progress.ts deleted file mode 100644 index 946b040c..00000000 --- a/packages/grid/src/utils/field/progress.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AITableField, isEmpty } from '../../index'; -import { Field } from './field'; - -export class ProgressField extends Field { - cellFullText(transformValue: number, field: AITableField): string[] { - let fullText: string[] = []; - if (!isEmpty(transformValue)) { - fullText.push(`${transformValue}%`); - } - return fullText; - } -} diff --git a/packages/grid/src/utils/field/select.ts b/packages/grid/src/utils/field/select.ts deleted file mode 100644 index 56f6a66d..00000000 --- a/packages/grid/src/utils/field/select.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Field } from './field'; -import { AITableField, AITableSelectField } from '../../index'; - -export class SelectField extends Field { - cellFullText(transformValue: string[], field: AITableField): string[] { - let cellText: string[] = []; - if (transformValue && Array.isArray(transformValue) && transformValue.length) { - transformValue.forEach((optionId) => { - const item = (field as AITableSelectField).settings?.options?.find((option) => option._id === optionId); - if (item?.text) { - cellText.push(item.text); - } - }); - } - return cellText; - } -} diff --git a/packages/grid/src/utils/field/text.ts b/packages/grid/src/utils/field/text.ts deleted file mode 100644 index d3e4a183..00000000 --- a/packages/grid/src/utils/field/text.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AITableField, AITableReferences, isEmpty } from '../../index'; -import { Field } from './field'; - -export class TextField extends Field { - cellFullText(transformValue: any, field: AITableField, references?: AITableReferences): string[] { - let fullText: string[] = []; - if (!isEmpty(transformValue)) { - fullText.push(String(transformValue)); - } - return fullText; - } -} diff --git a/packages/grid/src/utils/index.ts b/packages/grid/src/utils/index.ts index 7ea10776..0c07b11a 100644 --- a/packages/grid/src/utils/index.ts +++ b/packages/grid/src/utils/index.ts @@ -9,4 +9,5 @@ export * from './position'; export * from './style'; export * from './text-measure'; export * from './visible-range'; -export * from './field'; +export * from './field/model'; +export * from './field/operate'; diff --git a/packages/state/src/types/view.ts b/packages/state/src/types/view.ts index 2a702247..9b947dd7 100644 --- a/packages/state/src/types/view.ts +++ b/packages/state/src/types/view.ts @@ -1,4 +1,4 @@ -import { AITableField, AITableRecord } from '@ai-table/grid'; +import { AITableField, AITableFilterCondition, AITableRecord } from '@ai-table/grid'; import { Id } from 'ngx-tethys/types'; export class Positions { @@ -32,23 +32,6 @@ export enum AITableFilterLogical { or = 'or' } -export enum AITableFilterOperation { - eq = 'eq', - gte = 'gte', - lte = 'lte', - gt = 'gt', - lt = 'lt', - in = 'in', - contain = 'contain', - ne = 'ne', - nin = 'nin', - between = 'between', - besides = 'besides', - empty = 'empty', - exists = 'exists', - notContain = 'not_contain' -} - export type ViewSettings = AITableSearchOptions & AITableFilterConditions & AITableSortOptions; export interface AITableView { @@ -59,12 +42,6 @@ export interface AITableView { settings?: ViewSettings; } -export interface AITableFilterCondition { - field_id: Id; - operation: AITableFilterOperation; - value: TValue; -} - export interface AITableFilterConditions { condition_logical?: AITableFilterLogical; conditions?: AITableFilterCondition[]; diff --git a/packages/state/src/utils/common.ts b/packages/state/src/utils/common.ts index 91a96d21..1147baea 100644 --- a/packages/state/src/utils/common.ts +++ b/packages/state/src/utils/common.ts @@ -1,19 +1,6 @@ import { Path } from '@ai-table/grid'; -import { isUndefinedOrNull, isEmpty as isArrayEmpty, isArray, isObject } from 'ngx-tethys/util'; import { AITableView, AITableViewFields, AITableViewRecords } from '../types'; -export function isEmpty(value: any) { - if (isArray(value)) { - return isArrayEmpty(value); - } - - if (isObject(value)) { - return Reflect.ownKeys(value).length === 0; - } - - return isUndefinedOrNull(value) || value === ''; -} - export function isPathEqual(path: Path, another: Path): boolean { return path.length === another.length && path.every((n, i) => n === another[i]); } diff --git a/packages/state/src/utils/field/model/field.ts b/packages/state/src/utils/field/model/field.ts deleted file mode 100644 index 2f5632f7..00000000 --- a/packages/state/src/utils/field/model/field.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { AITableField, AITableReferences, FieldValue } from '@ai-table/grid'; -import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; -import { isEmpty } from '../../common'; -import { isEqual } from 'lodash'; - -export const zhIntlCollator = typeof Intl !== 'undefined' ? new Intl.Collator('zh-CN') : undefined; - -export abstract class Field { - protected stringInclude(str: string, searchStr: string) { - return str.toLowerCase().includes(searchStr.trim().toLowerCase()); - } - - abstract cellValueToString(cellValue: FieldValue, field: AITableField, references?: AITableReferences): string | null; - - isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { - switch (condition.operation) { - case AITableFilterOperation.empty: - case AITableFilterOperation.exists: { - return this.isEmptyOrNot(condition.operation, cellValue); - } - default: { - return true; - } - } - } - - isEmptyOrNot(operation: AITableFilterOperation.empty | AITableFilterOperation.exists, cellValue: FieldValue) { - switch (operation) { - case AITableFilterOperation.empty: { - return isEmpty(cellValue); - } - case AITableFilterOperation.exists: { - return !isEmpty(cellValue); - } - default: { - throw new Error('compare operator type error'); - } - } - } - - eq(cv1: FieldValue, cv2: FieldValue): boolean { - return isEqual(cv1, cv2); - } - - compare(cellValue1: FieldValue, cellValue2: FieldValue, field: AITableField, references?: AITableReferences): number { - if (this.eq(cellValue1, cellValue2)) { - return 0; - } - if (cellValue1 == null) { - return -1; - } - if (cellValue2 == null) { - return 1; - } - - let str1 = this.cellValueToString(cellValue1, field, references); - let str2 = this.cellValueToString(cellValue2, field, references); - - if (str1 === str2) { - return 0; - } - if (str1 == null) { - return -1; - } - if (str2 == null) { - return 1; - } - - str1 = str1.trim(); - str2 = str2.trim(); - - // test pinyin sort - return str1 === str2 ? 0 : zhIntlCollator ? zhIntlCollator.compare(str1, str2) : str1.localeCompare(str2, 'zh-CN') > 0 ? 1 : -1; - } -} diff --git a/packages/state/src/utils/field/model/link.ts b/packages/state/src/utils/field/model/link.ts deleted file mode 100644 index 735049d3..00000000 --- a/packages/state/src/utils/field/model/link.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { isEmpty } from '../../common'; -import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; -import { Field } from './field'; -import { AITableField, FieldValue, LinkFieldValue } from '@ai-table/grid'; -import { isObject, isString } from 'ngx-tethys/util'; - -export class LinkField extends Field { - override isMeetFilter(condition: AITableFilterCondition, cellValue: FieldValue) { - const cellTextValue = this.cellValueToString(cellValue); - switch (condition.operation) { - case AITableFilterOperation.empty: - return isEmpty(cellTextValue); - case AITableFilterOperation.exists: - return !isEmpty(cellTextValue); - case AITableFilterOperation.contain: - return !isEmpty(cellTextValue) && this.stringInclude(cellTextValue, condition.value); - default: - return super.isMeetFilter(condition, cellTextValue); - } - } - - override eq(cv1: LinkFieldValue | string | null, cv2: LinkFieldValue | string | null): boolean { - return this.cellValueToString(cv1) === this.cellValueToString(cv2); - } - - override compare(cellValue1: FieldValue, cellValue2: FieldValue, field: AITableField): number { - const cellTextValue1 = this.cellValueToString(cellValue1); - const cellTextValue2 = this.cellValueToString(cellValue2); - - return super.compare(cellTextValue1, cellTextValue2, field); - } - - cellValueToString(cellValue: LinkFieldValue | string | null): string { - if (isString(cellValue)) { - return cellValue; - } - if (isObject(cellValue)) { - return cellValue.text; - } - return ''; - } -} diff --git a/packages/state/src/utils/field/model/member.ts b/packages/state/src/utils/field/model/member.ts deleted file mode 100644 index 74aaa15d..00000000 --- a/packages/state/src/utils/field/model/member.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { isEmpty } from '../../common'; -import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; -import { Field } from './field'; -import { AITableField, AITableReferences, MemberFieldValue } from '@ai-table/grid'; - -export class MemberField extends Field { - override isMeetFilter(condition: AITableFilterCondition, cellValue: MemberFieldValue) { - switch (condition.operation) { - case AITableFilterOperation.empty: - return isEmpty(cellValue); - case AITableFilterOperation.exists: - return !isEmpty(cellValue); - case AITableFilterOperation.in: - return Array.isArray(condition.value) && hasIntersect(cellValue, condition.value); - case AITableFilterOperation.nin: - return Array.isArray(condition.value) && !hasIntersect(cellValue, condition.value); - default: - return super.isMeetFilter(condition, cellValue); - } - } - - cellValueToString(cellValue: MemberFieldValue, field: AITableField, references: AITableReferences): string | null { - let names: string[] = []; - if (cellValue?.length && references) { - for (let index = 0; index < cellValue.length; index++) { - const userInfo = references?.members[cellValue[index]]; - if (!userInfo) { - continue; - } - if (userInfo.display_name_pinyin) { - names.push(userInfo.display_name_pinyin); - } - } - } - return names && names.length ? names.join(', ') : null; - } -} - -function hasIntersect(array1: T[], array2: T[]) { - if (!Array.isArray(array1) || !Array.isArray(array2)) { - return false; - } - const set1 = new Set(array1); - const set2 = new Set(array2); - for (const ele of set1) { - if (set2.has(ele)) { - return true; - } - } - return false; -} diff --git a/packages/state/src/utils/field/model/select.ts b/packages/state/src/utils/field/model/select.ts deleted file mode 100644 index d4f6a2db..00000000 --- a/packages/state/src/utils/field/model/select.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { isEmpty } from '../../common'; -import { AITableFilterCondition, AITableFilterOperation } from '../../../types'; -import { Field } from './field'; -import { AITableField, AITableSelectOption, SelectFieldValue, SelectSettings } from '@ai-table/grid'; -import { Id } from 'ngx-tethys/types'; - -export class SelectField extends Field { - override isMeetFilter(condition: AITableFilterCondition, cellValue: SelectFieldValue) { - switch (condition.operation) { - case AITableFilterOperation.empty: - return isEmpty(cellValue); - case AITableFilterOperation.exists: - return !isEmpty(cellValue); - case AITableFilterOperation.in: - return Array.isArray(condition.value) && hasIntersect(cellValue, condition.value); - case AITableFilterOperation.nin: - return Array.isArray(condition.value) && !hasIntersect(cellValue, condition.value); - default: - return super.isMeetFilter(condition, cellValue); - } - } - - cellValueToString(cellValue: SelectFieldValue, field: AITableField): string | null { - return this.arrayValueToString(this.cellValueToArray(cellValue, field)); - } - - cellValueToArray(cellValue: SelectFieldValue, field: AITableField): string[] | null { - if (!cellValue) { - return null; - } - const result: string[] = []; - for (let i = 0, l = cellValue.length; i < l; i++) { - const option = this.findOptionById(field, cellValue[i]); - if (option) { - result.push(option.text); - } - } - return result; - } - - findOptionById(field: AITableField, id: Id): AITableSelectOption | null { - return (field.settings as SelectSettings).options.find(option => option._id === id) || null; - } - - arrayValueToString(cellValues: string[] | null): string | null { - return cellValues && cellValues.length ? cellValues.join(', ') : null; - } -} - -function hasIntersect(array1: T[], array2: T[]) { - if (!Array.isArray(array1) || !Array.isArray(array2)) { - return false; - } - const set1 = new Set(array1); - const set2 = new Set(array2); - for (const ele of set1) { - if (set2.has(ele)) { - return true; - } - } - return false; -} diff --git a/packages/state/src/utils/index.ts b/packages/state/src/utils/index.ts index fbcd4d13..32cb97df 100644 --- a/packages/state/src/utils/index.ts +++ b/packages/state/src/utils/index.ts @@ -1,5 +1,4 @@ export * from './build'; -export * from './field/model'; export * from './field/add-fields'; export * from './field/remove-field'; export * from './field/sort-fields'; diff --git a/packages/state/src/utils/record/filter.ts b/packages/state/src/utils/record/filter.ts index 234e0a6f..92ff2901 100644 --- a/packages/state/src/utils/record/filter.ts +++ b/packages/state/src/utils/record/filter.ts @@ -1,6 +1,14 @@ -import { AITableFieldType, AITableRecord, FieldValue, isSystemField, SystemFieldTypes } from '@ai-table/grid'; import { + AITableFieldType, AITableFilterCondition, + AITableRecord, + FieldValue, + isSystemField, + SystemFieldTypes, + ViewOperationMap, + isEmpty +} from '@ai-table/grid'; +import { AITableFilterConditions, AITableFilterLogical, AITableView, @@ -9,8 +17,6 @@ import { AITableViewRecords, AIViewTable } from '../../types'; -import { ViewOperationMap } from '../field/model'; -import { isEmpty } from '../common'; export function getFilteredRecords(aiTable: AIViewTable, records: AITableViewRecords, fields: AITableViewFields, activeView: AITableView) { const { conditions, condition_logical } = activeView.settings || {}; diff --git a/packages/state/src/utils/record/sort.ts b/packages/state/src/utils/record/sort.ts index 4f78f711..93fd5229 100644 --- a/packages/state/src/utils/record/sort.ts +++ b/packages/state/src/utils/record/sort.ts @@ -1,6 +1,5 @@ -import { AITable, AITableQueries } from '@ai-table/grid'; +import { AITable, AITableQueries, ViewOperationMap } from '@ai-table/grid'; import { AITableView, AITableViewRecords } from '../../types'; -import { ViewOperationMap } from '../field/model'; import { sortByViewPosition } from '../common'; export function getSortRecords(aiTable: AITable, records: AITableViewRecords, activeView: AITableView) {