Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support views share #WIK-16187 #22

Merged
merged 8 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/grid/src/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@
}

.grid-cell {
min-height: 44px;
max-height: 148px;
height: 44px;
display: flex;
align-items: center;
width: 300px;
Expand Down
46 changes: 35 additions & 11 deletions src/app/action/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AITableView>, 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<AITableView> = {};
const newView: Partial<AITableView> = {};
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 = {
Expand All @@ -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;
};

Expand Down
1 change: 0 additions & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
Expand Down
2 changes: 2 additions & 0 deletions src/app/component/basic/basic.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<a thyAction class="mb-2" thyIcon="trash" href="javascript:;" (click)="removeRecord()">删除行</a>
<a thyAction class="mb-2" thyIcon="move" href="javascript:;" (click)="moveRecord()">移动选中行到第三行</a>
<a thyAction class="mb-2" thyIcon="move" href="javascript:;" (click)="moveField()">移动选中列到第三列</a>
<a thyAction class="mb-2" thyIcon="sort" href="javascript:;" (click)="sort()">排序</a>

<ai-table-grid
[(aiRecords)]="records"
[(aiFields)]="fields"
Expand Down
20 changes: 17 additions & 3 deletions src/app/component/common/common.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ 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 { AITableView, AIViewTable, Direction, RowHeight } from '../../types/view';
import { FieldPropertyEditor } from './field-property-editor/field-property-editor.component';

const LOCAL_STORAGE_KEY = 'ai-table-data';
Expand Down Expand Up @@ -59,7 +59,7 @@ const initValue = {
'column-1': '文本 3-1',
'column-2': '3',
'column-3': {},
'column-4': 1,
'column-4': 2,
'column-5': 50
}
}
Expand Down Expand Up @@ -146,8 +146,8 @@ export class CommonComponent implements OnInit, AfterViewInit, OnDestroy {
aiTable!: AITable;

views = signal([
{ rowHeight: RowHeight.short, id: '3', name: '表格视图3' },
{ rowHeight: RowHeight.short, id: 'view1', name: '表格1', isActive: true },
{ rowHeight: RowHeight.short, id: '3', name: '表格视图3' }
]);

plugins = [withCustomApply];
Expand Down Expand Up @@ -221,6 +221,14 @@ export class CommonComponent implements OnInit, AfterViewInit, OnDestroy {
records: data.records
})
);
if(data.actions[0].type === 'set_view'){
const sortCondition = this.activeView().sortCondition;
if(sortCondition){
const {sortBy, direction} = sortCondition.conditions[0]
const records = this.records().sort((a:AITableRecord,b:AITableRecord)=>{ return direction === Direction.ascending ? a.values[sortBy] - b.values[sortBy] : b.values[sortBy] - a.values[sortBy] });
this.records.set([...records])
}
}
}

aiTableInitialized(aiTable: AITable) {
Expand Down Expand Up @@ -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 {}
}
2 changes: 2 additions & 0 deletions src/app/component/share/share.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<a thyAction class="mb-2" thyIcon="trash" href="javascript:;" (click)="removeRecord()">删除行</a>
<a thyAction class="mb-2" thyIcon="move" href="javascript:;" (click)="moveRecord()">移动选中行到第三行</a>
<a thyAction class="mb-2" thyIcon="move" href="javascript:;" (click)="moveField()">移动选中列到第三列</a>
<a thyAction class="mb-2" thyIcon="sort" href="javascript:;" (click)="sort()">排序</a>

<ai-table-grid
[(aiRecords)]="records"
[(aiFields)]="fields"
Expand Down
4 changes: 3 additions & 1 deletion src/app/component/share/share.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export class ShareComponent extends CommonComponent implements OnInit, AfterView
this.sharedType = getSharedType(
{
records: this.records(),
fields: this.fields()
fields: this.fields(),
views: this.views()
},
!!isInitializeSharedType
);
Expand All @@ -62,6 +63,7 @@ export class ShareComponent extends CommonComponent implements OnInit, AfterView
const data = translateSharedTypeToTable(this.sharedType!);
this.records.set(data.records);
this.fields.set(data.fields);
this.views.set(data.views);
isInitialized = true;
console.log(this.fields());
} else {
Expand Down
4 changes: 3 additions & 1 deletion src/app/share/apply-to-table/array-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { ActionName, AIFieldValuePath, AITable, AITableAction, AITableField, AIT
import * as Y from 'yjs';
import { toTablePath, translateRecord } from '../utils/translate-to-table';
import { isArray } from 'ngx-tethys/util';
import { AIViewTable, ViewActionName } from '../../types/view';

export default function translateArrayEvent(aiTable: AITable, event: Y.YEvent<any>): AITableAction[] {
const actions: AITableAction[] = [];
let offset = 0;
let targetPath = toTablePath(event.path);
const isRecordsTranslate = event.path.includes('records');
const isFieldsTranslate = event.path.includes('fields');

event.changes.delta.forEach((delta) => {
if ('retain' in delta) {
offset += delta.retain ?? 0;
Expand Down Expand Up @@ -60,6 +61,7 @@ export default function translateArrayEvent(aiTable: AITable, event: Y.YEvent<an
}
});
}

}
}
});
Expand Down
32 changes: 21 additions & 11 deletions src/app/share/apply-to-table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,38 @@ import * as Y from 'yjs';
import { AITable, AITableAction } from '@ai-table/grid';
import translateArrayEvent from './array-event';
import { YjsAITable } from '../yjs-table';
import { AIViewAction, AIViewTable } from '../../types/view';
import translateMapEvent from './map-event';

export function translateYjsEvent(aiTable: AITable, event: Y.YEvent<any>): AITableAction[] {
export function translateYjsEvent(aiTable: AITable, event: Y.YEvent<any>): 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<any>[]){
events.forEach((event) =>
translateYjsEvent(aiTable, event).forEach((item: AIViewAction| AITableAction) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的 event 类型应该是 Y.YMapEvent 了,不能在 translateArrayEvent 中处理了,关于 map 的处理可以参考下 https://github.com/worktile/y-slate/blob/main/src/apply-to-slate/map-event.ts

if(item.type === 'set_view'){
(aiTable as AIViewTable).viewApply(item)
}else {
aiTable.apply(item);
}

})
);
}

export function applyYjsEvents(aiTable: AITable, events: Y.YEvent<any>[]): 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)
});
}
}
45 changes: 45 additions & 0 deletions src/app/share/apply-to-table/map-event.ts
Original file line number Diff line number Diff line change
@@ -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<unknown>
): 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<AITableView> = {};
const properties:Partial<AITableView> = {};

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

6 changes: 4 additions & 2 deletions src/app/share/apply-to-yjs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ 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<O extends AITableAction = AITableAction> = {
[K in O['type']]: O extends { type: K } ? ApplyFunc<O> : never;
};

export type ApplyFunc<O extends AITableAction = AITableAction> = (sharedType: SharedType, op: O) => SharedType;

export const actionMappers: Partial<ActionMapper<AITableAction>> = {
export const actionMappers: Partial<ActionMapper<any>> = {
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 {
Expand Down
28 changes: 28 additions & 0 deletions src/app/share/apply-to-yjs/set-view.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion src/app/share/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
};
7 changes: 7 additions & 0 deletions src/app/share/shared.ts
Original file line number Diff line number Diff line change
@@ -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<any>;
export type SyncArrayElement = Y.Array<Y.Array<any>>;
Expand All @@ -11,6 +12,7 @@ export const getSharedType = (
initializeValue: {
fields: AITableFields;
records: AITableRecords;
views: AITableView[]
},
isInitializeSharedType: boolean
) => {
Expand All @@ -27,6 +29,7 @@ export function toSharedType(
data: {
fields: AITableFields;
records: AITableRecords;
views: AITableView[]
}
): void {
const fieldSharedType = new Y.Array();
Expand All @@ -36,6 +39,10 @@ export function toSharedType(
const recordSharedType = new Y.Array<Y.Array<any>>();
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 {
Expand Down
4 changes: 3 additions & 1 deletion src/app/share/utils/translate-to-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export const translateSharedTypeToTable = (sharedType: SharedType) => {
values: translateRecord(customField, fields)
};
});
const views = data['views']
return {
records,
fields
fields,
views
};
};

Expand Down
Loading