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]; }