From 1c5cd0c67e1787d460ec8e123eecad6521e77c5a Mon Sep 17 00:00:00 2001 From: Maple13 Date: Fri, 2 Aug 2024 14:58:24 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8feat(grid):=20optimize=20styles=20?= =?UTF-8?q?#WIK-16181=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/grid/src/styles/styles.scss | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/grid/src/styles/styles.scss b/packages/grid/src/styles/styles.scss index d0bd77dc..89c6a98d 100644 --- a/packages/grid/src/styles/styles.scss +++ b/packages/grid/src/styles/styles.scss @@ -9,6 +9,12 @@ .grid-header { border-top: 1px solid variables.$gray-200; + + .grid-field:not(.highlight) { + &:hover { + background-color: variables.$gray-80; + } + } } .grid-body { @@ -47,10 +53,11 @@ justify-content: space-between; .autofill-container { position: absolute; - width: 4px; - height: 4px; + width: 8px; + height: 8px; right: 0; bottom: 0; + border: 2px solid variables.$white; background: variables.$primary; cursor: crosshair; z-index: 100; @@ -142,7 +149,7 @@ } .thy-icon-plus { - font-size: 14px; + font-size: 16px; color: variables.$gray-600; } } From 37d8a80f5370053e09cd3acad65596d84265ecb8 Mon Sep 17 00:00:00 2001 From: huanhuanwa <2323666215@qq.com> Date: Fri, 2 Aug 2024 15:00:43 +0800 Subject: [PATCH 2/5] build: release 0.0.4 --- CHANGELOG.md | 18 ++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- packages/grid/package.json | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9e786c5..6c6b62cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [0.0.4](https://github.com/worktile/ai-table/compare/0.0.3...0.0.4) (2024-08-02) + + +### Features + +* **action:** add AIPlugin ([#18](https://github.com/worktile/ai-table/issues/18)) ([024fa5f](https://github.com/worktile/ai-table/commit/024fa5fa8502febddef9b0b8ac1cc4802dc30451)) +* add shared demo ([#15](https://github.com/worktile/ai-table/issues/15)) ([488a8e1](https://github.com/worktile/ai-table/commit/488a8e182daa5d0d8709153b87e3256c4c7239b5)) +* apply icon and width #WIK-16166 ([cf46f10](https://github.com/worktile/ai-table/commit/cf46f102e5471ca19ed873e4daf0642cb4066ec3)), closes [#WIK-16166](https://github.com/worktile/ai-table/issues/WIK-16166) +* **core:** add set_field action #WIK-16194 ([#20](https://github.com/worktile/ai-table/issues/20)) ([6b34150](https://github.com/worktile/ai-table/commit/6b34150b78058da8b8f1b9e60e97524b6165abee)), closes [#WIK-16194](https://github.com/worktile/ai-table/issues/WIK-16194) +* **core:** support move_record and move_field #WIK-16196 ([#24](https://github.com/worktile/ai-table/issues/24)) ([3ebf3fc](https://github.com/worktile/ai-table/commit/3ebf3fc7801fb7780cc118388f5418ae8992a483)), closes [#WIK-16196](https://github.com/worktile/ai-table/issues/WIK-16196) +* **core:** support remove_field and remove_record #WIK-16192 ([#19](https://github.com/worktile/ai-table/issues/19)) ([2bc106e](https://github.com/worktile/ai-table/commit/2bc106ebbae3305e17ec2592ab3422c3042a46b4)), closes [#WIK-16192](https://github.com/worktile/ai-table/issues/WIK-16192) +* **demo:** add shared demo #WIK-16223 ([#27](https://github.com/worktile/ai-table/issues/27)) ([009a234](https://github.com/worktile/ai-table/commit/009a2348d9336fb111605b04c9ac18087a53725c)), closes [#WIK-16223](https://github.com/worktile/ai-table/issues/WIK-16223) +* **demo:** add ws server ([#23](https://github.com/worktile/ai-table/issues/23)) ([bd7bf6b](https://github.com/worktile/ai-table/commit/bd7bf6b1db525954dd5e35f4f83343066382640d)) +* **selection:** add selection feature #WIK-16060 ([#12](https://github.com/worktile/ai-table/issues/12)) ([0392723](https://github.com/worktile/ai-table/commit/039272385a3b32f6d0a874e863a9a1d87301f8c0)), closes [#WIK-16060](https://github.com/worktile/ai-table/issues/WIK-16060) +* **types:** adjust field type and record value #WIK-16187 ([#21](https://github.com/worktile/ai-table/issues/21)) ([2938add](https://github.com/worktile/ai-table/commit/2938add3ebf4497b0367b6499bc8c163c395dc3b)), closes [#WIK-16187](https://github.com/worktile/ai-table/issues/WIK-16187) + + + ## [0.0.3](https://github.com/worktile/ai-table/compare/0.0.2...0.0.3) (2024-07-22) diff --git a/package-lock.json b/package-lock.json index 653693bc..aa0475e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ai-table", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ai-table", - "version": "0.0.3", + "version": "0.0.4", "workspaces": [ "packages/*" ], diff --git a/package.json b/package.json index c637092a..9d5810ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ai-table", - "version": "0.0.3", + "version": "0.0.4", "workspaces": [ "packages/*" ], diff --git a/packages/grid/package.json b/packages/grid/package.json index f8d92c2f..e645ab03 100644 --- a/packages/grid/package.json +++ b/packages/grid/package.json @@ -1,6 +1,6 @@ { "name": "@ai-table/grid", - "version": "0.0.3", + "version": "0.0.4", "peerDependencies": { "@angular/common": "^18.0.0", "@angular/core": "^18.0.0" From a71336d6e2b6c3c8cdafa93c857f094fbeddb423 Mon Sep 17 00:00:00 2001 From: Maple13 Date: Fri, 2 Aug 2024 16:31:32 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=A8feat(grid):=20support=20progress?= =?UTF-8?q?=20editor=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - support hover pop-up editing component --- package.json | 2 +- .../abstract-cell-editor.component.ts | 2 +- .../progress/progress-editor.component.scss | 21 +++ .../progress/progress-editor.component.ts | 58 ++++++++ .../rating/rating-editor.component.ts | 5 +- .../text/text-editor.component.ts | 11 +- packages/grid/src/components/index.ts | 8 +- packages/grid/src/constants/editor.ts | 22 ++- packages/grid/src/constants/grid.ts | 11 +- packages/grid/src/core/constants/field.ts | 8 +- packages/grid/src/core/types/core.ts | 4 +- packages/grid/src/grid.component.html | 20 ++- packages/grid/src/grid.component.ts | 138 ++++++++++++------ packages/grid/src/services/event.service.ts | 54 ++++--- packages/grid/src/styles/styles.scss | 1 + src/app/app.component.ts | 5 +- src/app/app.config.ts | 17 ++- src/app/component/common/common.component.ts | 52 ++++--- src/tsconfig.json | 8 + tsconfig.json | 3 +- 20 files changed, 317 insertions(+), 133 deletions(-) create mode 100644 packages/grid/src/components/cell-editors/progress/progress-editor.component.scss create mode 100644 packages/grid/src/components/cell-editors/progress/progress-editor.component.ts create mode 100644 src/tsconfig.json diff --git a/package.json b/package.json index 9d5810ec..69f5906c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@tethys/icons": "^1.4.62", "date-fns": "^3.6.0", "immer": "^10.0.3", - "ngx-tethys": "^17.0.8", + "ngx-tethys": "^17.0.14", "npm": "^10.8.1", "rxjs": "~7.8.0", "tslib": "^2.6.3", diff --git a/packages/grid/src/components/cell-editors/abstract-cell-editor.component.ts b/packages/grid/src/components/cell-editors/abstract-cell-editor.component.ts index 6803ad72..4a4e9456 100644 --- a/packages/grid/src/components/cell-editors/abstract-cell-editor.component.ts +++ b/packages/grid/src/components/cell-editors/abstract-cell-editor.component.ts @@ -32,6 +32,6 @@ export abstract class AbstractEditCellEditor + {{ modelValue }}{{ config?.suffix || '%' }} + `, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FormsModule, ThySlider], + host: { + class: 'progress-editor' + } +}) +export class ProgressEditorComponent extends AbstractEditCellEditor implements OnInit { + config: Partial = { + max: 100, + min: 0, + step: 1, + progressType: 'success', + suffix: '%', + size: 'md' + }; + + @HostListener('mousedown', ['$event']) + mousedownHandler(event: Event) { + event.preventDefault(); + } + + constructor() { + super(); + } + + updateValue(value: number) { + this.updateFieldValue(); + } +} diff --git a/packages/grid/src/components/cell-editors/rating/rating-editor.component.ts b/packages/grid/src/components/cell-editors/rating/rating-editor.component.ts index bbe40654..37bf3914 100644 --- a/packages/grid/src/components/cell-editors/rating/rating-editor.component.ts +++ b/packages/grid/src/components/cell-editors/rating/rating-editor.component.ts @@ -1,15 +1,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { AbstractEditCellEditor } from '../abstract-cell-editor.component'; import { ThyRate } from 'ngx-tethys/rate'; -import { ThyTooltipModule } from 'ngx-tethys/tooltip'; +import { AbstractEditCellEditor } from '../abstract-cell-editor.component'; @Component({ selector: 'rating-cell-editor', template: ` `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [FormsModule, ThyRate, ThyTooltipModule] + imports: [FormsModule, ThyRate] }) export class RatingCellEditorComponent extends AbstractEditCellEditor { updateValue() { diff --git a/packages/grid/src/components/cell-editors/text/text-editor.component.ts b/packages/grid/src/components/cell-editors/text/text-editor.component.ts index c981343e..b452c38a 100644 --- a/packages/grid/src/components/cell-editors/text/text-editor.component.ts +++ b/packages/grid/src/components/cell-editors/text/text-editor.component.ts @@ -7,14 +7,9 @@ import { AbstractEditCellEditor } from '../abstract-cell-editor.component'; @Component({ selector: 'text-cell-editor', - template: ` `, + template: ` + + `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgIf, FormsModule, ThyAutofocusDirective, ThyInputDirective, ThyEnterDirective] diff --git a/packages/grid/src/components/index.ts b/packages/grid/src/components/index.ts index 3cba1bcf..74bfbf8a 100644 --- a/packages/grid/src/components/index.ts +++ b/packages/grid/src/components/index.ts @@ -1 +1,7 @@ -export * from './field-property-editor/field-property-editor.component' \ No newline at end of file +export * from './cell-editors/date-time/date-time-editor.component'; +export * from './cell-editors/link/number-editor.component'; +export * from './cell-editors/number/number-editor.component'; +export * from './cell-editors/progress/progress-editor.component'; +export * from './cell-editors/rating/rating-editor.component'; +export * from './cell-editors/text/text-editor.component'; +export * from './field-property-editor/field-property-editor.component'; diff --git a/packages/grid/src/constants/editor.ts b/packages/grid/src/constants/editor.ts index 7d035506..21caea78 100644 --- a/packages/grid/src/constants/editor.ts +++ b/packages/grid/src/constants/editor.ts @@ -1,16 +1,22 @@ -import { AITableFieldType } from '../core'; -import { TextCellEditorComponent } from '../components/cell-editors/text/text-editor.component'; -import { NumberCellEditorComponent } from '../components/cell-editors/number/number-editor.component'; -import { DateTimeCellEditorComponent } from '../components/cell-editors/date-time/date-time-editor.component'; -import { RatingCellEditorComponent } from '../components/cell-editors/rating/rating-editor.component'; -import { LinkCellEditorComponent } from '../components/cell-editors/link/number-editor.component'; +import { + DateTimeCellEditorComponent, + LinkCellEditorComponent, + NumberCellEditorComponent, + ProgressEditorComponent, + RatingCellEditorComponent, + TextCellEditorComponent +} from '../components'; import { SelectCellEditorComponent } from '../components/cell-editors/select/select-editor.component'; +import { AITableFieldType } from '../core'; -export const GRID_CELL_EDITOR_MAP: Partial> = { +export const GRID_CELL_EDITOR_MAP: Record = { [AITableFieldType.text]: TextCellEditorComponent, + [AITableFieldType.richText]: TextCellEditorComponent, [AITableFieldType.select]: SelectCellEditorComponent, [AITableFieldType.number]: NumberCellEditorComponent, + [AITableFieldType.member]: null, [AITableFieldType.date]: DateTimeCellEditorComponent, [AITableFieldType.rate]: RatingCellEditorComponent, - [AITableFieldType.link]: LinkCellEditorComponent + [AITableFieldType.link]: LinkCellEditorComponent, + [AITableFieldType.progress]: ProgressEditorComponent }; diff --git a/packages/grid/src/constants/grid.ts b/packages/grid/src/constants/grid.ts index caeb3db1..f550ab57 100644 --- a/packages/grid/src/constants/grid.ts +++ b/packages/grid/src/constants/grid.ts @@ -1,15 +1,12 @@ -import { AITable, AITableFieldType } from '../core'; +import { AITableFieldType } from '../core'; export const DEFAULT_COLUMN_WIDTH = 200; export const MIN_COLUMN_WIDTH = 80; -export const DBL_CLICK_EDIT_TYPE = [ - AITableFieldType.text, - AITableFieldType.number, - AITableFieldType.select, - AITableFieldType.date -]; +export const DBL_CLICK_EDIT_TYPE = [AITableFieldType.text, AITableFieldType.number, AITableFieldType.select, AITableFieldType.date]; + +export const MOUSEOVER_EDIT_TYPE = [AITableFieldType.progress]; export const RowHeight = { Short: 32, diff --git a/packages/grid/src/core/constants/field.ts b/packages/grid/src/core/constants/field.ts index e24d8049..eaf475f3 100644 --- a/packages/grid/src/core/constants/field.ts +++ b/packages/grid/src/core/constants/field.ts @@ -1,5 +1,5 @@ -import { AITableFieldInfo, AITableFieldType } from '../types'; import { helpers } from 'ngx-tethys/util'; +import { AITableFieldInfo, AITableFieldType } from '../types'; export const BasicFields = [ { @@ -37,6 +37,12 @@ export const BasicFields = [ name: '链接', icon: 'link-insert', width: 300 + }, + { + type: AITableFieldType.progress, + name: '进度', + icon: 'progress', + width: 200 } ]; diff --git a/packages/grid/src/core/types/core.ts b/packages/grid/src/core/types/core.ts index d59f3f90..0c177b43 100644 --- a/packages/grid/src/core/types/core.ts +++ b/packages/grid/src/core/types/core.ts @@ -1,7 +1,7 @@ import { WritableSignal } from '@angular/core'; -import { AITableAction } from './action'; -import { AITableSelection } from '../../types'; import { Id } from 'ngx-tethys/types'; +import { AITableSelection } from '../../types'; +import { AITableAction } from './action'; export enum AITableFieldType { text = 'text', // 包含多行文本 diff --git a/packages/grid/src/grid.component.html b/packages/grid/src/grid.component.html index ad80a2fd..e6b8e146 100644 --- a/packages/grid/src/grid.component.html +++ b/packages/grid/src/grid.component.html @@ -47,6 +47,7 @@ @for (field of gridData().fields; track $index) {
@switch (field.type) { @case (AITableFieldType.select) { @@ -73,13 +73,25 @@ @case (AITableFieldType.link) { {{ record.values[field.id]?.text }} + {{ record.values[field.id]?.text }} + + } + @case (AITableFieldType.progress) { + + {{ record.values[field.id] }}{{ record.values[field.id]?.config?.suffix || '%' }} + } @default { {{ record.values[field.id] }} diff --git a/packages/grid/src/grid.component.ts b/packages/grid/src/grid.component.ts index a5cdc4b3..33aca06e 100644 --- a/packages/grid/src/grid.component.ts +++ b/packages/grid/src/grid.component.ts @@ -1,39 +1,53 @@ -import { ChangeDetectionStrategy, Component, computed, ElementRef, input, model, NgZone, OnInit, output, signal } from '@angular/core'; import { CommonModule, NgClass, NgComponentOutlet, NgForOf } from '@angular/common'; -import { SelectOptionPipe } from './pipes/grid'; -import { ThyTag } from 'ngx-tethys/tag'; -import { ThyPopoverModule } from 'ngx-tethys/popover'; +import { + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + ElementRef, + inject, + input, + model, + NgZone, + OnInit, + output, + signal +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { buildGridData } from './utils'; -import { AIFieldConfig, AITableFieldMenuItem, AITableRowHeight } from './types'; +import { FormsModule } from '@angular/forms'; +import { ThyAction } from 'ngx-tethys/action'; +import { ThyCheckboxModule } from 'ngx-tethys/checkbox'; +import { ThyDatePickerFormatPipe } from 'ngx-tethys/date-picker'; +import { ThyDropdownDirective, ThyDropdownMenuComponent } from 'ngx-tethys/dropdown'; +import { ThyFlexibleText } from 'ngx-tethys/flexible-text'; +import { ThyIcon } from 'ngx-tethys/icon'; +import { ThyPopoverModule, ThyPopoverRef } from 'ngx-tethys/popover'; +import { ThyProgress } from 'ngx-tethys/progress'; +import { ThyRate } from 'ngx-tethys/rate'; +import { ThyStopPropagationDirective } from 'ngx-tethys/shared'; +import { ThyTag } from 'ngx-tethys/tag'; +import { ProgressEditorComponent } from './components'; +import { FieldMenu } from './components/field-menu/field-menu.component'; +import { AITableFieldPropertyEditor } from './components/field-property-editor/field-property-editor.component'; +import { DBL_CLICK_EDIT_TYPE, DefaultFieldMenus, MOUSEOVER_EDIT_TYPE } from './constants'; import { Actions, - createAITable, - getDefaultRecord, + AIPlugin, AITable, AITableChangeOptions, AITableFields, AITableFieldType, AITableRecords, + createAITable, createDefaultField, - AIPlugin + getDefaultRecord } from './core'; -import { ThyIcon } from 'ngx-tethys/icon'; +import { SelectOptionPipe } from './pipes/grid'; import { AITableGridEventService } from './services/event.service'; -import { AITableFieldPropertyEditor } from './components/field-property-editor/field-property-editor.component'; -import { ThyDatePickerFormatPipe } from 'ngx-tethys/date-picker'; -import { ThyRate } from 'ngx-tethys/rate'; -import { FormsModule } from '@angular/forms'; -import { ThyFlexibleText } from 'ngx-tethys/flexible-text'; -import { ThyTooltipModule, ThyTooltipService } from 'ngx-tethys/tooltip'; -import { ThyCheckboxModule } from 'ngx-tethys/checkbox'; -import { ThyStopPropagationDirective } from 'ngx-tethys/shared'; -import { FieldMenu } from './components/field-menu/field-menu.component'; -import { ThyAction } from 'ngx-tethys/action'; -import { ThyDropdownDirective, ThyDropdownMenuComponent } from 'ngx-tethys/dropdown'; -import { DefaultFieldMenus } from './constants'; import { AI_TABLE_GRID_FIELD_SERVICE_MAP, AITableGridFieldService } from './services/field.service'; import { AITableGridSelectionService } from './services/selection.servive'; +import { AIFieldConfig, AITableFieldMenuItem, AITableRowHeight } from './types'; +import { buildGridData } from './utils'; @Component({ selector: 'ai-table-grid', @@ -54,18 +68,19 @@ import { AITableGridSelectionService } from './services/selection.servive'; ThyPopoverModule, ThyIcon, ThyRate, + ThyProgress, AITableFieldPropertyEditor, ThyDatePickerFormatPipe, - ThyTooltipModule, ThyFlexibleText, ThyStopPropagationDirective, FieldMenu, ThyAction, ThyDropdownDirective, ThyDropdownMenuComponent, - ThyCheckboxModule + ThyCheckboxModule, + ProgressEditorComponent ], - providers: [ThyTooltipService, AITableGridEventService, AITableGridFieldService, AITableGridSelectionService] + providers: [AITableGridEventService, AITableGridFieldService, AITableGridSelectionService] }) export class AITableGrid implements OnInit { aiRecords = model.required(); @@ -82,8 +97,6 @@ export class AITableGrid implements OnInit { AITableFieldType = AITableFieldType; - takeUntilDestroyed = takeUntilDestroyed(); - aiTable!: AITable; get isSelectedAll() { @@ -96,29 +109,24 @@ export class AITableGrid implements OnInit { fieldMenus!: AITableFieldMenuItem[]; + mouseoverRef!: ThyPopoverRef; + gridData = computed(() => { return buildGridData(this.aiRecords(), this.aiFields(), this.aiTable.selection()); }); - constructor( - private elementRef: ElementRef, - private aiTableGridEventService: AITableGridEventService, - public aiTableGridSelectionService: AITableGridSelectionService, - private aiTableGridFieldService: AITableGridFieldService, - private ngZone: NgZone - ) {} + private ngZone = inject(NgZone); + private elementRef = inject(ElementRef); + private destroyRef = inject(DestroyRef); + private aiTableGridFieldService = inject(AITableGridFieldService); + private aiTableGridEventService = inject(AITableGridEventService); + public aiTableGridSelectionService = inject(AITableGridSelectionService); ngOnInit(): void { this.initAITable(); this.initService(); this.buildFieldMenus(); - this.ngZone.runOutsideAngular(() => { - this.aiTableGridEventService.mousedownEvent$.pipe(this.takeUntilDestroyed).subscribe((event) => { - if ((event as MouseEvent)?.target) { - this.aiTableGridSelectionService.updateSelect(event as MouseEvent); - } - }); - }); + this.subscribeEvents(); } initAITable() { @@ -164,4 +172,52 @@ export class AITableGrid implements OnInit { const field = signal(createDefaultField(this.aiTable, AITableFieldType.text)); this.aiTableGridFieldService.editFieldProperty(gridColumnBlank, this.aiTable, field, false); } + + private subscribeEvents() { + this.ngZone.runOutsideAngular(() => { + this.aiTableGridEventService.dblClickEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { + this.dblClick(event); + }); + this.aiTableGridEventService.mousedownEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { + if ((event as MouseEvent)?.target) { + this.aiTableGridSelectionService.updateSelect(event as MouseEvent); + } + }); + this.aiTableGridEventService.mouseoverEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { + this.mouseoverHandle(event); + }); + this.aiTableGridEventService.globalMouseoverEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { + this.closeHoverCellEditor(event); + }); + }); + } + + private dblClick(event: MouseEvent) { + const cellDom = (event.target as HTMLElement).closest('.grid-cell') as HTMLElement; + const type = cellDom && (cellDom.getAttribute('type')! as AITableFieldType); + if (type && DBL_CLICK_EDIT_TYPE.includes(type)) { + this.aiTableGridEventService.openEdit(cellDom); + } + } + + private mouseoverHandle(event: MouseEvent) { + if (this.mouseoverRef) { + this.mouseoverRef?.close(); + } + const cellDom = (event.target as HTMLElement).closest('.grid-cell') as HTMLElement; + const type = cellDom && (cellDom.getAttribute('type')! as AITableFieldType); + if (type && MOUSEOVER_EDIT_TYPE.includes(type)) { + this.mouseoverRef = this.aiTableGridEventService.openEdit(cellDom); + } + } + + private closeHoverCellEditor(e: MouseEvent) { + if (this.mouseoverRef) { + const hasGrid = e.target && (e.target as HTMLElement).closest('.ai-table-grid'); + const hasCellEditor = e.target && (e.target as HTMLElement).closest('.grid-cell-editor'); + if (!hasGrid && !hasCellEditor) { + this.mouseoverRef.close(); + } + } + } } diff --git a/packages/grid/src/services/event.service.ts b/packages/grid/src/services/event.service.ts index 489fdf16..918753ff 100644 --- a/packages/grid/src/services/event.service.ts +++ b/packages/grid/src/services/event.service.ts @@ -1,12 +1,11 @@ -import { Injectable, Signal } from '@angular/core'; +import { DestroyRef, inject, Injectable, Signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { fromEvent, Subject } from 'rxjs'; -import { DBL_CLICK_EDIT_TYPE } from '../constants'; -import { getRecordOrField } from '../utils'; -import { AITable, AITableField, AITableFieldType, AITableRecord } from '../core'; -import { GRID_CELL_EDITOR_MAP } from '../constants/editor'; import { ThyPopover } from 'ngx-tethys/popover'; +import { debounceTime, fromEvent, Subject } from 'rxjs'; +import { GRID_CELL_EDITOR_MAP } from '../constants/editor'; +import { AITable, AITableField, AITableFieldType, AITableRecord } from '../core'; import { AITableGridCellRenderSchema } from '../types'; +import { getRecordOrField } from '../utils'; @Injectable() export class AITableGridEventService { @@ -14,11 +13,17 @@ export class AITableGridEventService { aiFieldRenderers?: Partial>; - takeUntilDestroyed = takeUntilDestroyed(); + dblClickEvent$ = new Subject(); mousedownEvent$ = new Subject(); - constructor(private thyPopover: ThyPopover) {} + mouseoverEvent$ = new Subject(); + + globalMouseoverEvent$ = new Subject(); + + private destroyRef = inject(DestroyRef); + + private thyPopover = inject(ThyPopover); initialize(aiTable: AITable, aiFieldRenderers?: Partial>) { this.aiTable = aiTable; @@ -27,24 +32,28 @@ export class AITableGridEventService { registerEvents(element: HTMLElement) { fromEvent(element, 'dblclick') - .pipe(this.takeUntilDestroyed) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((event) => { - this.dblClick(event as MouseEvent); + this.dblClickEvent$.next(event); }); - fromEvent(element, 'mousedown') - .pipe(this.takeUntilDestroyed) + fromEvent(element, 'mouseover') + .pipe(debounceTime(80), takeUntilDestroyed(this.destroyRef)) .subscribe((event) => { - this.mousedownEvent$.next(event as MouseEvent); + this.mouseoverEvent$.next(event); }); - } - private dblClick(event: MouseEvent) { - const cellDom = (event.target as HTMLElement).closest('.grid-cell') as HTMLElement; - const type = cellDom && cellDom.getAttribute('type')! as AITableFieldType; - if (type && DBL_CLICK_EDIT_TYPE.includes(type)) { - this.openEdit(cellDom); - } + fromEvent(document, 'mouseover') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((event) => { + this.globalMouseoverEvent$.next(event); + }); + + fromEvent(element, 'mousedown') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((event) => { + this.mousedownEvent$.next(event); + }); } private getEditorComponent(type: AITableFieldType) { @@ -54,14 +63,14 @@ export class AITableGridEventService { return GRID_CELL_EDITOR_MAP[type]; } - private openEdit(cellDom: HTMLElement) { + openEdit(cellDom: HTMLElement) { const { x, y, width, height } = cellDom.getBoundingClientRect(); const fieldId = cellDom.getAttribute('fieldId')!; const recordId = cellDom.getAttribute('recordId')!; const field = getRecordOrField(this.aiTable.fields, fieldId) as Signal; const record = getRecordOrField(this.aiTable.records, recordId) as Signal; const component = this.getEditorComponent(field().type); - this.thyPopover.open(component, { + const ref = this.thyPopover.open(component, { origin: cellDom, originPosition: { x: x - 1, @@ -85,5 +94,6 @@ export class AITableGridEventService { manualClosure: true, animationDisabled: true }); + return ref; } } diff --git a/packages/grid/src/styles/styles.scss b/packages/grid/src/styles/styles.scss index 89c6a98d..16edf128 100644 --- a/packages/grid/src/styles/styles.scss +++ b/packages/grid/src/styles/styles.scss @@ -2,6 +2,7 @@ @use 'ngx-tethys/styles/index.scss'; @use 'ngx-tethys/styles/variables.scss'; @use '../components/cell-editors/cell-editor.scss'; +@use '../components/cell-editors/progress/progress-editor.component.scss'; .ai-table-grid { display: table; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 072c63c8..2b46dc17 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; @Component({ @@ -6,6 +6,7 @@ import { RouterOutlet } from '@angular/router'; standalone: true, imports: [RouterOutlet], templateUrl: './app.component.html', - styleUrl: './app.component.scss' + styleUrl: './app.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent {} diff --git a/src/app/app.config.ts b/src/app/app.config.ts index bb114ba9..207267aa 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,9 +1,18 @@ -import { ApplicationConfig, inject, provideZoneChangeDetection } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; import { provideAnimations } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; +import { ThyTooltipModule } from 'ngx-tethys/tooltip'; import { routes } from './app.routes'; -import { provideHttpClient } from '@angular/common/http'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideHttpClient(), provideAnimations(), provideRouter(routes)] + providers: [ + importProvidersFrom(BrowserModule, FormsModule, ThyTooltipModule), + provideZoneChangeDetection({ eventCoalescing: true }), + provideHttpClient(), + provideAnimations(), + provideRouter(routes) + ] }; diff --git a/src/app/component/common/common.component.ts b/src/app/component/common/common.component.ts index 400123c7..bf570ab5 100644 --- a/src/app/component/common/common.component.ts +++ b/src/app/component/common/common.component.ts @@ -1,40 +1,30 @@ import { - AfterViewInit, - Component, - OnDestroy, - computed, - OnInit, - Signal, - signal, - WritableSignal, - output, -} from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; -import { RouterOutlet } from '@angular/router'; -import { + Actions, + AIFieldConfig, + AIFieldPath, + AIRecordPath, + AITable, + AITableField, AITableFields, AITableFieldType, AITableGrid, + AITableQueries, + AITableRecord, AITableRecords, - AITableField, - AITable, - AIFieldConfig, - EditFieldPropertyItem, DividerMenuItem, - RemoveFieldItem, - Actions, - AIFieldPath, - AIRecordPath, - AITableQueries, - AITableRecord + EditFieldPropertyItem, + RemoveFieldItem } from '@ai-table/grid'; +import { AfterViewInit, Component, computed, OnDestroy, OnInit, output, Signal, signal, WritableSignal } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { RouterOutlet } from '@angular/router'; +import { ThyAction } from 'ngx-tethys/action'; import { ThyIconRegistry } from 'ngx-tethys/icon'; import { ThyPopoverModule } from 'ngx-tethys/popover'; import { CustomActions } from '../../action'; import { withCustomApply } from '../../plugins/custom-action.plugin'; import { AITableView, AIViewTable, RowHeight } from '../../types/view'; import { FieldPropertyEditor } from './field-property-editor/field-property-editor.component'; -import { ThyAction } from 'ngx-tethys/action'; const LOCAL_STORAGE_KEY = 'ai-table-data'; @@ -49,7 +39,8 @@ const initValue = { url: 'https://www.baidu.com', text: '百度链接' }, - 'column-4': 3 + 'column-4': 3, + 'column-5': 10 } }, { @@ -58,7 +49,8 @@ const initValue = { 'column-1': '文本 2-1', 'column-2': '2', 'column-3': {}, - 'column-4': 1 + 'column-4': 1, + 'column-5': 20 } }, { @@ -67,7 +59,8 @@ const initValue = { 'column-1': '文本 3-1', 'column-2': '3', 'column-3': {}, - 'column-4': 1 + 'column-4': 1, + 'column-5': 50 } } ], @@ -108,6 +101,11 @@ const initValue = { id: 'column-4', name: '评分', type: AITableFieldType.rate + }, + { + id: 'column-5', + name: '进度', + type: AITableFieldType.progress } ] }; diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 00000000..07d43ce9 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": [] + }, + "exclude": ["**/*.spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index deeceb08..9425457b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "useDefineForClassFields": false, "lib": ["ES2022", "dom"], "paths": { - "@ai-table/grid": ["./dist/grid"] + "@ai-table/grid": ["./packages/grid/src/public-api"], + "@ai-table/grid/*": ["./packages/grid/src/*"] } }, "angularCompilerOptions": { From 0b70d3da0731de85204eef658b8187c4a9151030 Mon Sep 17 00:00:00 2001 From: Maple13 Date: Mon, 5 Aug 2024 09:47:26 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8feat(grid):=20click=20on=20a=20bla?= =?UTF-8?q?nk=20space=20to=20deselect=20#WIK-16181=20(#31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨feat(grid): optimize styles #WIK-16181 - click on a blank space to deselect - use textarea to support text editing and limit the height * ✨feat(grid): click on a blank space to deselect - use textarea to support text editing and limit the height --- .../components/cell-editors/cell-editor.scss | 9 ++++ .../text/text-editor.component.ts | 49 +++++++++++++++++-- packages/grid/src/grid.component.ts | 12 +++-- packages/grid/src/services/event.service.ts | 11 ++++- .../grid/src/services/selection.servive.ts | 9 +++- 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/packages/grid/src/components/cell-editors/cell-editor.scss b/packages/grid/src/components/cell-editors/cell-editor.scss index 7e9ff161..1b70a7d3 100644 --- a/packages/grid/src/components/cell-editors/cell-editor.scss +++ b/packages/grid/src/components/cell-editors/cell-editor.scss @@ -3,3 +3,12 @@ height: 100%; } } + +.text-cell-editor { + display: block; + + textarea { + min-height: 44px; + resize: none; + } +} diff --git a/packages/grid/src/components/cell-editors/text/text-editor.component.ts b/packages/grid/src/components/cell-editors/text/text-editor.component.ts index b452c38a..ca2606c8 100644 --- a/packages/grid/src/components/cell-editors/text/text-editor.component.ts +++ b/packages/grid/src/components/cell-editors/text/text-editor.component.ts @@ -1,5 +1,5 @@ import { NgIf } from '@angular/common'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, inject, Renderer2 } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ThyInputDirective } from 'ngx-tethys/input'; import { ThyAutofocusDirective, ThyEnterDirective } from 'ngx-tethys/shared'; @@ -8,13 +8,54 @@ import { AbstractEditCellEditor } from '../abstract-cell-editor.component'; @Component({ selector: 'text-cell-editor', template: ` - + `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [NgIf, FormsModule, ThyAutofocusDirective, ThyInputDirective, ThyEnterDirective] + imports: [NgIf, FormsModule, ThyAutofocusDirective, ThyInputDirective, ThyEnterDirective], + host: { + class: 'd-block' + } }) -export class TextCellEditorComponent extends AbstractEditCellEditor { +export class TextCellEditorComponent extends AbstractEditCellEditor implements AfterViewInit { + private elementRef = inject(ElementRef); + + private render2 = inject(Renderer2); + + private maxHeight = 148; + + constructor() { + super(); + } + + ngAfterViewInit() { + setTimeout(() => { + this.updateStyle(); + }); + } + + updateStyle() { + const textarea = this.elementRef.nativeElement.querySelector('textarea'); + const height = textarea.scrollHeight <= this.maxHeight ? textarea.scrollHeight : this.maxHeight; + + this.render2.setStyle(textarea, 'height', `${height}px`); + this.render2.setStyle(textarea, 'min-height', `44px`); + this.render2.setStyle(textarea, 'max-height', `${this.maxHeight}px`); + this.render2.setStyle(textarea, 'resize', 'none'); + } + + valueChange() { + this.updateStyle(); + } + updateValue() { this.updateFieldValue(); this.closePopover(); diff --git a/packages/grid/src/grid.component.ts b/packages/grid/src/grid.component.ts index 33aca06e..c3a609d0 100644 --- a/packages/grid/src/grid.component.ts +++ b/packages/grid/src/grid.component.ts @@ -26,6 +26,7 @@ import { ThyProgress } from 'ngx-tethys/progress'; import { ThyRate } from 'ngx-tethys/rate'; import { ThyStopPropagationDirective } from 'ngx-tethys/shared'; import { ThyTag } from 'ngx-tethys/tag'; +import { mergeWith } from 'rxjs/operators'; import { ProgressEditorComponent } from './components'; import { FieldMenu } from './components/field-menu/field-menu.component'; import { AITableFieldPropertyEditor } from './components/field-property-editor/field-property-editor.component'; @@ -178,11 +179,12 @@ export class AITableGrid implements OnInit { this.aiTableGridEventService.dblClickEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { this.dblClick(event); }); - this.aiTableGridEventService.mousedownEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { - if ((event as MouseEvent)?.target) { - this.aiTableGridSelectionService.updateSelect(event as MouseEvent); - } - }); + this.aiTableGridEventService.mousedownEvent$ + .pipe(mergeWith(this.aiTableGridEventService.globalMousedownEvent$), takeUntilDestroyed(this.destroyRef)) + .subscribe((event) => { + this.aiTableGridSelectionService.updateSelect(event); + }); + this.aiTableGridEventService.mouseoverEvent$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => { this.mouseoverHandle(event); }); diff --git a/packages/grid/src/services/event.service.ts b/packages/grid/src/services/event.service.ts index 918753ff..f75c15f7 100644 --- a/packages/grid/src/services/event.service.ts +++ b/packages/grid/src/services/event.service.ts @@ -21,6 +21,8 @@ export class AITableGridEventService { globalMouseoverEvent$ = new Subject(); + globalMousedownEvent$ = new Subject(); + private destroyRef = inject(DestroyRef); private thyPopover = inject(ThyPopover); @@ -54,6 +56,12 @@ export class AITableGridEventService { .subscribe((event) => { this.mousedownEvent$.next(event); }); + + fromEvent(document, 'mousedown') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((event) => { + this.globalMousedownEvent$.next(event as MouseEvent); + }); } private getEditorComponent(type: AITableFieldType) { @@ -92,7 +100,8 @@ export class AITableGridEventService { outsideClosable: false, hasBackdrop: false, manualClosure: true, - animationDisabled: true + animationDisabled: true, + autoAdaptive: true }); return ref; } diff --git a/packages/grid/src/services/selection.servive.ts b/packages/grid/src/services/selection.servive.ts index d909f6b1..25caba82 100644 --- a/packages/grid/src/services/selection.servive.ts +++ b/packages/grid/src/services/selection.servive.ts @@ -52,7 +52,11 @@ export class AITableGridSelectionService { } updateSelect(event: MouseEvent) { - const target = event.target as HTMLElement; + const target = event?.target as HTMLElement; + if (!target) { + return; + } + const cellDom = target.closest('.grid-cell'); const colDom = target.closest('.grid-field'); if (cellDom) { @@ -64,5 +68,8 @@ export class AITableGridSelectionService { const fieldId = colDom.getAttribute('fieldId'); fieldId && this.selectField(fieldId); } + if (!cellDom && !colDom) { + this.clearSelection(); + } } } From 93e28abf7154e9cff0d7404af7b033ce81841396 Mon Sep 17 00:00:00 2001 From: cmm-va <33084123+cmm-va@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:50:31 +0800 Subject: [PATCH 5/5] feat: support views share #WIK-16187 (#22) --- packages/grid/src/styles/styles.scss | 3 +- src/app/action/view.ts | 46 +++++++++++++++----- src/app/app.component.ts | 1 - src/app/component/basic/basic.component.html | 2 + src/app/component/common/common.component.ts | 20 +++++++-- src/app/component/share/share.component.html | 2 + src/app/component/share/share.component.ts | 4 +- src/app/share/apply-to-table/array-event.ts | 4 +- src/app/share/apply-to-table/index.ts | 32 +++++++++----- src/app/share/apply-to-table/map-event.ts | 45 +++++++++++++++++++ src/app/share/apply-to-yjs/index.ts | 6 ++- src/app/share/apply-to-yjs/set-view.ts | 28 ++++++++++++ src/app/share/provider.ts | 2 +- src/app/share/shared.ts | 7 +++ src/app/share/utils/translate-to-table.ts | 4 +- src/app/types/view.ts | 11 +++-- 16 files changed, 180 insertions(+), 37 deletions(-) create mode 100644 src/app/share/apply-to-table/map-event.ts create mode 100644 src/app/share/apply-to-yjs/set-view.ts diff --git a/packages/grid/src/styles/styles.scss b/packages/grid/src/styles/styles.scss index 16edf128..7f9da975 100644 --- a/packages/grid/src/styles/styles.scss +++ b/packages/grid/src/styles/styles.scss @@ -42,8 +42,7 @@ } .grid-cell { - min-height: 44px; - max-height: 148px; + height: 44px; display: flex; align-items: center; width: 300px; diff --git a/src/app/action/view.ts b/src/app/action/view.ts index 17834c87..48e16d64 100644 --- a/src/app/action/view.ts +++ b/src/app/action/view.ts @@ -2,18 +2,28 @@ import { AITableView, AIViewAction, ViewActionName } from '../types/view'; import { AIViewTable } from '../types/view'; import { createDraft, finishDraft } from 'immer'; -export function setView(aiTable: AIViewTable, newView: AITableView, path: [number]) { +export function setView(aiTable: AIViewTable, value: Partial, path: [number]) { const [index] = path; - const view = aiTable.views()[index]; - if (JSON.stringify(view) !== JSON.stringify(newView)) { - const operation = { - type: ViewActionName.setView, - view, - newView, - path - }; - aiTable.viewApply(operation); + const view: AITableView = aiTable.views()[index]; + const oldView: Partial = {}; + const newView: Partial = {}; + for (const key in value) { + const k = key as keyof AITableView; + if (JSON.stringify(view[k]) !== JSON.stringify(value[k])) { + if (view.hasOwnProperty(key)) { + oldView[k] = view[k] as any; + } + newView[k] = value[k] as any; + } } + const operation = { + type: ViewActionName.setView, + view: oldView, + newView, + path + }; + aiTable.viewApply(operation); + } export const GeneralActions = { @@ -28,7 +38,21 @@ export const GeneralActions = { export const applyView = (aiTable: AIViewTable, views: AITableView[], options: AIViewAction) => { const [viewIndex] = options.path; - views[viewIndex] = options.newView; + const targetView: AITableView = views[viewIndex] + Object.entries(options.newView).forEach(([k, value]) => { + const key = k as keyof AITableView; + if (value == null) { + delete targetView[key] + } else { + targetView[key] = value as never + } + }); + Object.entries(options.view).forEach(([k, value]) => { + if (!options.newView.hasOwnProperty(k)) { + const key = k as keyof AITableView; + delete targetView[key] + } + }); return views; }; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2b46dc17..4d7bc9da 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; - @Component({ selector: 'app-root', standalone: true, diff --git a/src/app/component/basic/basic.component.html b/src/app/component/basic/basic.component.html index dcd2dccc..f495fc6b 100644 --- a/src/app/component/basic/basic.component.html +++ b/src/app/component/basic/basic.component.html @@ -1,6 +1,8 @@ 删除行 移动选中行到第三行 移动选中列到第三列 +排序 + { return direction === Direction.ascending ? a.values[sortBy] - b.values[sortBy] : b.values[sortBy] - a.values[sortBy] }); + this.records.set([...records]) + } + } } aiTableInitialized(aiTable: AITable) { @@ -283,5 +291,11 @@ export class CommonComponent implements OnInit, AfterViewInit, OnDestroy { }); } + sort(){ + const direction = this.activeView().sortCondition?.conditions[0].direction + const sortCondition = { keepSort:true , conditions:[{sortBy: 'column-4', direction: direction=== Direction.ascending ? Direction.descending: Direction.ascending}]} + CustomActions.setView(this.aiTable as any, {sortCondition}, [1]); + } + ngOnDestroy(): void {} } diff --git a/src/app/component/share/share.component.html b/src/app/component/share/share.component.html index 4a0ba273..0d935d41 100644 --- a/src/app/component/share/share.component.html +++ b/src/app/component/share/share.component.html @@ -8,6 +8,8 @@ 删除行 移动选中行到第三行 移动选中列到第三列 +排序 + ): AITableAction[] { const actions: AITableAction[] = []; @@ -9,7 +10,7 @@ export default function translateArrayEvent(aiTable: AITable, event: Y.YEvent { if ('retain' in delta) { offset += delta.retain ?? 0; @@ -60,6 +61,7 @@ export default function translateArrayEvent(aiTable: AITable, event: Y.YEvent): AITableAction[] { +export function translateYjsEvent(aiTable: AITable, event: Y.YEvent): AITableAction[] | AIViewAction[] { if (event instanceof Y.YArrayEvent) { return translateArrayEvent(aiTable, event); } + if (event instanceof Y.YMapEvent) { + return translateMapEvent(aiTable, event); + } return []; } +export function applyEvents(aiTable: AITable, events: Y.YEvent[]){ + events.forEach((event) => + translateYjsEvent(aiTable, event).forEach((item: AIViewAction| AITableAction) => { + if(item.type === 'set_view'){ + (aiTable as AIViewTable).viewApply(item) + }else { + aiTable.apply(item); + } + + }) + ); +} + export function applyYjsEvents(aiTable: AITable, events: Y.YEvent[]): void { if (YjsAITable.isUndo(aiTable)) { - events.forEach((event) => - translateYjsEvent(aiTable, event).forEach((item) => { - aiTable.apply(item); - }) - ); + applyEvents(aiTable, events) } else { YjsAITable.asRemote(aiTable, () => { - events.forEach((event) => - translateYjsEvent(aiTable, event).forEach((item) => { - aiTable.apply(item); - }) - ); + applyEvents(aiTable, events) }); } } diff --git a/src/app/share/apply-to-table/map-event.ts b/src/app/share/apply-to-table/map-event.ts new file mode 100644 index 00000000..d06167aa --- /dev/null +++ b/src/app/share/apply-to-table/map-event.ts @@ -0,0 +1,45 @@ +import { AITable, AITableAction } from "@ai-table/grid"; +import * as Y from 'yjs'; +import { AITableView, AIViewAction, AIViewTable, ViewActionName } from "../../types/view"; +import { toTablePath } from "../utils/translate-to-table"; +import { SharedType } from "../shared"; + +export default function translateMapEvent( + aiTable: AITable, + event: Y.YMapEvent +): AIViewAction[] | AITableAction[] { + const isViewTranslate = event.path.includes('views') + if (isViewTranslate) { + let [targetPath] = toTablePath(event.path) as [number]; + const targetSyncElement = event.target as SharedType; + const targetElement = (aiTable as AIViewTable).views()[targetPath] + const keyChanges: [string, { action: 'add' | 'update' | 'delete', oldValue: any }][] = Array.from(event.changes.keys.entries()); + const newProperties:Partial = {}; + const properties:Partial = {}; + + const entries:[string, any][] = keyChanges.map(([key, info]) => { + const value = targetSyncElement.get(key); + return [ + key, + info.action === 'delete' ? null : value instanceof Y.AbstractType ? value.toJSON() : value + ] + }) + + for (const [key, value] of entries) { + const k = key as keyof AITableView; + newProperties[k] = value + } + + const oldEntries = keyChanges.map(([key]) => [key, (targetElement as any)[key]]) + + for (const [key, value] of oldEntries) { + const k = key as keyof AITableView; + properties[k] = value + } + + return [{ type: ViewActionName.setView, view: properties, newView: newProperties, path: [targetPath] }]; + + } + return [] +} + diff --git a/src/app/share/apply-to-yjs/index.ts b/src/app/share/apply-to-yjs/index.ts index 83eb2ca6..0163ff02 100644 --- a/src/app/share/apply-to-yjs/index.ts +++ b/src/app/share/apply-to-yjs/index.ts @@ -3,6 +3,7 @@ import { SharedType } from '../shared'; import updateFieldValue from './update-field-value'; import addRecord from './add-record'; import addField from './add-field'; +import setView from './set-view'; export type ActionMapper = { [K in O['type']]: O extends { type: K } ? ApplyFunc : never; @@ -10,10 +11,11 @@ export type ActionMapper = { export type ApplyFunc = (sharedType: SharedType, op: O) => SharedType; -export const actionMappers: Partial> = { +export const actionMappers: Partial> = { update_field_value: updateFieldValue, add_record: addRecord, - add_field: addField + add_field: addField, + set_view: setView }; export default function applyActionOps(sharedType: SharedType, actions: AITableAction[], aiTable: AITable): SharedType { diff --git a/src/app/share/apply-to-yjs/set-view.ts b/src/app/share/apply-to-yjs/set-view.ts new file mode 100644 index 00000000..3ed0e2dd --- /dev/null +++ b/src/app/share/apply-to-yjs/set-view.ts @@ -0,0 +1,28 @@ + +import { isObject } from "ngx-tethys/util"; +import { AIViewAction } from "../../types/view"; +import { SharedType, toSyncElement } from "../shared"; + +export default function setView(sharedType: SharedType, action: AIViewAction): SharedType { + const views = sharedType.get('views'); + if (views) { + const index = action.path[0]; + const targetView = views.get(index) as any; + Object.entries(action.newView).forEach(([key, value]) => { + if (value == null) { + targetView.delete(key); + } else if(isObject(value)){ + targetView.set(key, toSyncElement(value)); + }else{ + targetView.set(key,value); + } + }); + + Object.entries(action.view).forEach(([key]) => { + if (!action.newView.hasOwnProperty(key)) { + targetView.delete(key); + } + }); + } + return sharedType; +} diff --git a/src/app/share/provider.ts b/src/app/share/provider.ts index 8c8dbd54..7b507fe3 100644 --- a/src/app/share/provider.ts +++ b/src/app/share/provider.ts @@ -8,4 +8,4 @@ export const connectProvider = (doc: Y.Doc, room: string, isDev: boolean) => { const provider = new WebsocketProvider(isDev ? devUrl : prodUrl, room, doc); provider.connect(); return provider; -}; +}; \ No newline at end of file diff --git a/src/app/share/shared.ts b/src/app/share/shared.ts index 8d7fe83a..698d7776 100644 --- a/src/app/share/shared.ts +++ b/src/app/share/shared.ts @@ -1,6 +1,7 @@ import { AITableFields, AITableRecord, AITableRecords } from '@ai-table/grid'; import { isArray, isObject } from 'ngx-tethys/util'; import * as Y from 'yjs'; +import { AITableView } from '../types/view'; export type SyncMapElement = Y.Map; export type SyncArrayElement = Y.Array>; @@ -11,6 +12,7 @@ export const getSharedType = ( initializeValue: { fields: AITableFields; records: AITableRecords; + views: AITableView[] }, isInitializeSharedType: boolean ) => { @@ -27,6 +29,7 @@ export function toSharedType( data: { fields: AITableFields; records: AITableRecords; + views: AITableView[] } ): void { const fieldSharedType = new Y.Array(); @@ -36,6 +39,10 @@ export function toSharedType( const recordSharedType = new Y.Array>(); sharedType.set('records', recordSharedType); recordSharedType.insert(0, data.records.map(toRecordSyncElement)); + + const viewsSharedType = new Y.Array(); + sharedType.set('views', viewsSharedType); + viewsSharedType.insert(0, data.views.map(toSyncElement)) } export function toSyncElement(node: any): SyncMapElement { diff --git a/src/app/share/utils/translate-to-table.ts b/src/app/share/utils/translate-to-table.ts index 42dc83df..8e2f3939 100644 --- a/src/app/share/utils/translate-to-table.ts +++ b/src/app/share/utils/translate-to-table.ts @@ -20,9 +20,11 @@ export const translateSharedTypeToTable = (sharedType: SharedType) => { values: translateRecord(customField, fields) }; }); + const views = data['views'] return { records, - fields + fields, + views }; }; diff --git a/src/app/types/view.ts b/src/app/types/view.ts index 56769f8c..aea10d3d 100644 --- a/src/app/types/view.ts +++ b/src/app/types/view.ts @@ -1,5 +1,4 @@ import { AITable } from '@ai-table/grid'; -import { Direction } from '@angular/cdk/bidi'; import { Signal, WritableSignal } from '@angular/core'; export enum RowHeight { @@ -8,6 +7,12 @@ export enum RowHeight { tall = 'tall' } +export enum Direction { + default = 0, + ascending = 1, + descending = -1 +} + export interface AITableView { id: string; name: string; @@ -36,8 +41,8 @@ export enum ViewActionName { export interface AIViewAction { type: ViewActionName.setView; - view: AITableView; - newView: AITableView; + view: Partial; + newView: Partial; path: [number]; }