Skip to content

Commit

Permalink
feat: support add and extend field #WIK-16038 (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
huanhuanwa authored Jul 19, 2024
1 parent 195aae5 commit 74edbbb
Show file tree
Hide file tree
Showing 31 changed files with 349 additions and 99 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ChangeDetectionStrategy, Component, computed, inject, input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, inject, Input, input, OnInit } from '@angular/core';
import { ThyPopoverRef } from 'ngx-tethys/popover';
import { GridCellPath } from '../../types';
import { Actions, AITable, AITableField, AITableQueries, AITableRecord } from '../../core';
import { Actions, AIFieldValuePath, AITable, AITableField, AITableQueries, AITableRecord } from '../../core';

@Component({
selector: 'abstract-edit-cell',
Expand All @@ -14,22 +13,22 @@ export abstract class AbstractEditCellEditor<TValue, TFieldType extends AITableF

record = input.required<AITableRecord>();

aiTable = input.required<AITable>();
@Input({ required: true }) aiTable!: AITable;

modelValue!: TValue;

protected thyPopoverRef = inject(ThyPopoverRef<AbstractEditCellEditor<TValue>>);

ngOnInit(): void {
this.modelValue = computed(() => {
const path = AITableQueries.findPath(this.aiTable(), this.field(), this.record()) as GridCellPath;
return AITableQueries.getFieldValue(this.aiTable(), path);
const path = AITableQueries.findPath(this.aiTable, this.field(), this.record()) as AIFieldValuePath;
return AITableQueries.getFieldValue(this.aiTable, path);
})();
}

updateFieldValue() {
const path = AITableQueries.findPath(this.aiTable(), this.field(), this.record()) as GridCellPath;
Actions.updateFieldValue(this.aiTable(), this.modelValue, path);
const path = AITableQueries.findPath(this.aiTable, this.field(), this.record()) as AIFieldValuePath;
Actions.updateFieldValue(this.aiTable, this.modelValue, path);
}

closePopover() {
Expand Down
10 changes: 10 additions & 0 deletions packages/grid/src/components/field-menu/field-menu.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@for (menu of fieldMenus; track index; let index = $index) {
@if (menu.id === 'divider') {
<thy-divider [thyStyle]="'solid'"></thy-divider>
} @else {
<a thyDropdownMenuItem href="javascript:;" (click)="execute(menu)">
<thy-icon [thyIconName]="menu.icon!"></thy-icon>
<span>{{ menu.name! }}</span>
</a>
}
}
40 changes: 40 additions & 0 deletions packages/grid/src/components/field-menu/field-menu.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Component, ChangeDetectionStrategy, Input, ElementRef, signal } from '@angular/core';
import { AITableFieldMenu } from '../../types/field';
import { AITableField, AITable } from '../../core';
import {
ThyDropdownMenuItemDirective,
ThyDropdownMenuItemNameDirective,
ThyDropdownMenuItemIconDirective,
ThyDropdownMenuComponent
} from 'ngx-tethys/dropdown';
import { ThyIcon } from 'ngx-tethys/icon';
import { ThyDivider } from 'ngx-tethys/divider';

@Component({
selector: 'field-menu',
templateUrl: './field-menu.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ThyIcon,
ThyDivider,
ThyDropdownMenuComponent,
ThyDropdownMenuItemDirective,
ThyDropdownMenuItemNameDirective,
ThyDropdownMenuItemIconDirective
]
})
export class FieldMenu {
@Input({ required: true }) field!: AITableField;

@Input({ required: true }) aiTable!: AITable;

@Input({ required: true }) fieldMenus!: AITableFieldMenu[];

@Input() origin!: HTMLElement | ElementRef<any>;

execute(menu: AITableFieldMenu) {
const field = signal({ ...this.field });
menu.exec && menu.exec(this.aiTable, field, this.origin);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
thyAutofocus
name="fieldName"
[maxlength]="fieldMaxLength"
[(ngModel)]="field().name"
[(ngModel)]="aiField().name"
required
placeholder="输入列名称"
[thyUniqueCheck]="checkUniqueName"
Expand All @@ -27,9 +27,15 @@
</thy-list-item>
</div>
</thy-form-group>
<ng-container *ngIf="aiExternalTemplate; else defaultTemplate">
<ng-container *ngTemplateOutlet="aiExternalTemplate"></ng-container>
</ng-container>
<ng-template #defaultTemplate>
<!-- TODO: 内部属性渲染 -->
</ng-template>
<thy-form-group-footer thyAlign="right">
<button thyButton="link-secondary" (click)="cancel()" thySize="sm">取消</button>
<button thyButton="primary" (thyFormSubmit)="addField()" thySize="sm">确定</button>
<button thyButton="primary" (thyFormSubmit)="editFieldProperty()" thySize="sm">确定</button>
</thy-form-group-footer>
</form>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NgForOf, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, OnInit, WritableSignal, computed, inject, input, signal } from '@angular/core';
import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, TemplateRef, booleanAttribute, computed, inject, model } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ThyInput, ThyInputCount, ThyInputGroup, ThyInputDirective } from 'ngx-tethys/input';
import { ThyConfirmValidatorDirective, ThyUniqueCheckValidator, ThyFormValidatorConfig, ThyFormModule } from 'ngx-tethys/form';
Expand All @@ -11,15 +11,14 @@ import {
ThyDropdownMenuItemIconDirective
} from 'ngx-tethys/dropdown';
import { ThyButton } from 'ngx-tethys/button';
import { of } from 'rxjs';
import { AITableField, AITableFieldType, AITableFields, idCreator } from '../../core';
import { AITable, AITableField, AITableFieldType, Actions, Fields, FieldsMap, createDefaultFieldName } from '../../core';
import { ThyIcon } from 'ngx-tethys/icon';
import { FieldTypes, FieldTypesMap } from '../../core/constants/field';
import { ThyPopoverRef } from 'ngx-tethys/popover';
import { ThyListItem } from 'ngx-tethys/list';
import { of } from 'rxjs';

@Component({
selector: 'field-property-editor',
selector: 'ai-table-field-property-editor',
templateUrl: './field-property-editor.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -41,7 +40,8 @@ import { ThyListItem } from 'ngx-tethys/list';
ThyDropdownMenuItemIconDirective,
ThyButton,
ThyFormModule,
ThyListItem
ThyListItem,
NgTemplateOutlet
],
host: {
class: 'field-property-editor d-block pl-5 pr-5 pb-5 pt-4'
Expand All @@ -54,15 +54,17 @@ import { ThyListItem } from 'ngx-tethys/list';
`
]
})
export class FieldPropertyEditorComponent implements OnInit {
fields = input.required<AITableFields>();
export class AITableFieldPropertyEditor {
aiField = model.required<AITableField>();

@Input({ required: true }) aiTable!: AITable;

@Input({ required: true }) confirmAction: ((field: AITableField) => void) | null = null;
@Input() aiExternalTemplate: TemplateRef<any> | null = null;

field: WritableSignal<AITableField> = signal({ id: idCreator(), type: AITableFieldType.Text, name: '' });
@Input({ transform: booleanAttribute }) isUpdate!: boolean;

fieldType = computed(() => {
return FieldTypesMap[this.field().type];
return FieldsMap[this.aiField().type];
});

fieldMaxLength = 32;
Expand All @@ -76,25 +78,27 @@ export class FieldPropertyEditorComponent implements OnInit {
}
};

selectableFields = FieldTypes;
selectableFields = Fields;

protected thyPopoverRef = inject(ThyPopoverRef<FieldPropertyEditorComponent>);
protected thyPopoverRef = inject(ThyPopoverRef<AITableFieldPropertyEditor>);

constructor() {}

ngOnInit() {}

checkUniqueName = (fieldName: string) => {
fieldName = fieldName?.trim();
return of(!!this.fields()?.find((field) => field.name === fieldName));
return of(!!this.aiTable.fields()?.find((field) => field.name === fieldName && this.aiField()?.id !== field.id));
};

selectFieldType(fieldType: AITableFieldType) {
this.field.update((item) => ({ ...item, type: fieldType }));
this.aiField.update((item) => ({ ...item, type: fieldType, name: createDefaultFieldName(this.aiTable, fieldType) }));
}

addField() {
this.confirmAction!(this.field());
editFieldProperty() {
if (this.isUpdate) {
//TODO: updateField
} else {
Actions.addField(this.aiTable, this.aiField(), [this.aiTable.fields().length]);
}
this.thyPopoverRef.close();
}

Expand Down
1 change: 1 addition & 0 deletions packages/grid/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './field-property-editor/field-property-editor.component'
20 changes: 20 additions & 0 deletions packages/grid/src/constants/field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AITable, AITableField } from '../core';
import { AITableFieldMenu } from '../types/field';
import { AI_TABLE_GRID_FIELD_SERVICE_MAP } from '../services/field.service';
import { ElementRef, WritableSignal } from '@angular/core';

export const DividerMenuItem = {
id: 'divider'
};

export const EditFieldPropertyItem = {
id: 'editFieldProperty',
name: '编辑列',
icon: 'edit',
exec: (aiTable: AITable, field: WritableSignal<AITableField>, origin?: HTMLElement | ElementRef<any>) => {
const fieldService = AI_TABLE_GRID_FIELD_SERVICE_MAP.get(aiTable);
origin && fieldService?.editFieldProperty(origin, aiTable, field, true);
}
};

export const DefaultFieldMenus: AITableFieldMenu[] = [EditFieldPropertyItem];
2 changes: 1 addition & 1 deletion packages/grid/src/constants/grid.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AITableFieldType } from '../core';
import { AITable, AITableFieldType } from '../core';

export const DEFAULT_COLUMN_WIDTH = 200;

Expand Down
1 change: 1 addition & 0 deletions packages/grid/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './grid';
export * from './field';
4 changes: 2 additions & 2 deletions packages/grid/src/core/action/field.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ActionName, AddFieldAction, FieldPath, AITable, AITableField } from '../types';
import { ActionName, AddFieldAction, AIFieldPath, AITable, AITableField } from '../types';

export function addField(aiTable: AITable, field: AITableField, path: [FieldPath]) {
export function addField(aiTable: AITable, field: AITableField, path: AIFieldPath) {
const operation: AddFieldAction = {
type: ActionName.AddField,
field,
Expand Down
6 changes: 3 additions & 3 deletions packages/grid/src/core/action/record.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActionName, AddRecordAction, FieldPath, RecordPath, UpdateFieldValueAction, AITable, AITableRecord } from '../types';
import { ActionName, AddRecordAction, AIRecordPath, UpdateFieldValueAction, AITable, AITableRecord, AIFieldValuePath } from '../types';
import { AITableQueries } from '../utils';

export function updateFieldValue(aiTable: AITable, value: any, path: [RecordPath, FieldPath]) {
export function updateFieldValue(aiTable: AITable, value: any, path: AIFieldValuePath) {
const node = AITableQueries.getFieldValue(aiTable, path);
if (node !== value) {
const operation: UpdateFieldValueAction = {
Expand All @@ -14,7 +14,7 @@ export function updateFieldValue(aiTable: AITable, value: any, path: [RecordPath
}
}

export function addRecord(aiTable: AITable, record: AITableRecord, path: [RecordPath]) {
export function addRecord(aiTable: AITable, record: AITableRecord, path: AIRecordPath) {
const operation: AddRecordAction = {
type: ActionName.AddRecord,
record,
Expand Down
8 changes: 4 additions & 4 deletions packages/grid/src/core/constants/field.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AITableFieldType } from '../types';
import { AITableFieldInfo, AITableFieldType } from '../types';
import { helpers } from 'ngx-tethys/util';

export const BasicFieldTypes = [
export const BasicFields = [
{
type: AITableFieldType.Text,
name: '文本',
Expand Down Expand Up @@ -34,6 +34,6 @@ export const BasicFieldTypes = [
}
];

export const FieldTypes = [...BasicFieldTypes];
export const Fields: AITableFieldInfo[] = [...BasicFields];

export const FieldTypesMap = helpers.keyBy([...FieldTypes], 'type');
export const FieldsMap = helpers.keyBy([...BasicFields], 'type');
1 change: 1 addition & 0 deletions packages/grid/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './types';
export * from './action';
export * from './utils';
export * from './constants/field';
14 changes: 8 additions & 6 deletions packages/grid/src/core/types/action.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { AITableField, AITableRecord } from './core';

export type RecordPath = number;
export type AIRecordPath = [number];

export type FieldPath = number;
export type AIFieldPath = [number];

export type Path = [RecordPath] | [FieldPath] | [RecordPath, FieldPath];
export type AIFieldValuePath = [number, number];

export type Path = AIRecordPath | AIFieldPath | AIFieldValuePath;

export enum ActionName {
UpdateFieldValue = 'update_field_value',
Expand All @@ -20,20 +22,20 @@ export enum ExecuteType {

export type UpdateFieldValueAction = {
type: ActionName.UpdateFieldValue;
path: [RecordPath, FieldPath];
path: AIFieldValuePath;
fieldValue: any;
newFieldValue: any;
};

export type AddRecordAction = {
type: ActionName.AddRecord;
path: [RecordPath];
path: AIRecordPath;
record: AITableRecord;
};

export type AddFieldAction = {
type: ActionName.AddField;
path: [FieldPath];
path: AIFieldPath;
field: AITableField;
};

Expand Down
6 changes: 6 additions & 0 deletions packages/grid/src/core/types/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@ export interface AITableChangeOptions {
fields: AITableField[];
actions: AITableAction[];
}

export interface AITableFieldInfo {
type: AITableFieldType;
name: string;
icon: string;
}
14 changes: 13 additions & 1 deletion packages/grid/src/core/utils/field.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { AITableFieldType } from '../types';
import { FieldsMap } from '../constants/field';
import { AITable, AITableFieldType } from '../types';
import { idCreator } from './id-creator';

export function getDefaultFieldValue(type: AITableFieldType) {
return '';
}

export function createDefaultFieldName(aiTable: AITable, type: AITableFieldType = AITableFieldType.Text) {
const fields = aiTable.fields();
const count = fields.filter((item) => item.type === type).length;
return count === 0 ? FieldsMap[type].name : FieldsMap[type].name + count;
}

export function createDefaultField(aiTable: AITable, type: AITableFieldType = AITableFieldType.Text) {
return { id: idCreator(), type, name: createDefaultFieldName(aiTable, type) };
}
10 changes: 5 additions & 5 deletions packages/grid/src/core/utils/queries.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { isUndefinedOrNull } from 'ngx-tethys/util';
import { FieldPath, Path, RecordPath, AITable, AITableField, AITableRecord } from '../types';
import { Path, AITable, AITableField, AITableRecord, AIFieldValuePath, AIRecordPath, AIFieldPath } from '../types';

export const AITableQueries = {
findPath(aiTable: AITable, field?: AITableField, record?: AITableRecord): Path {
const recordIndex = record && aiTable.records().indexOf(record);
const fieldIndex = field && aiTable.fields().indexOf(field);
if (!isUndefinedOrNull(recordIndex) && recordIndex > -1 && !isUndefinedOrNull(fieldIndex) && fieldIndex > -1) {
return [recordIndex!, fieldIndex!];
return [recordIndex!, fieldIndex!] as AIFieldValuePath;
}
if (!isUndefinedOrNull(recordIndex) && recordIndex > -1) {
return [recordIndex];
return [recordIndex] as AIRecordPath;
}
if (!isUndefinedOrNull(fieldIndex) && fieldIndex > -1) {
return [fieldIndex];
return [fieldIndex] as AIFieldPath;
}
throw new Error(`Unable to find the path: ${JSON.stringify({ ...(field || {}), ...(record || {}) })}`);
},
getFieldValue(aiTable: AITable, path: [RecordPath, FieldPath]): any {
getFieldValue(aiTable: AITable, path: [number, number]): any {
if (!aiTable || !aiTable.records() || !aiTable.fields()) {
throw new Error(`Cannot find a descendant at path [${path}]`);
}
Expand Down
Loading

0 comments on commit 74edbbb

Please sign in to comment.