Merge pull request #142 from webzard-io/yinsw/operation

fix adjust order operation
This commit is contained in:
tanbowensg 2021-11-29 16:06:53 +08:00 committed by GitHub
commit 2783efdc89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 3366 additions and 37 deletions

View File

@ -0,0 +1,440 @@
import { Application } from '../../core/lib';
export const ApplicationFixture: Record<string, Application> = {
adjustOrderOperation: {
kind: 'Application',
version: 'example/v1',
metadata: { name: 'dialog_component', description: 'dialog component example' },
spec: {
components: [
{
id: 'grid_layout1',
type: 'core/v1/grid_layout',
properties: {
layout: [
{
w: 10,
h: 15,
x: 0,
y: 0,
i: 'tabs1',
moved: false,
static: false,
isDraggable: true,
},
],
},
traits: [],
},
{
id: 'fetchUsers',
type: 'core/v1/dummy',
properties: {},
traits: [
{
type: 'core/v1/fetch',
properties: {
url: 'https://6177d4919c328300175f5b99.mockapi.io/users',
method: 'get',
lazy: false,
headers: {},
body: {},
onComplete: [],
},
},
],
},
{
id: 'usersTable',
type: 'chakra_ui/v1/table',
properties: {
data: '{{fetchUsers.fetch.data}}',
columns: [
{ key: 'username', title: '用户名', type: 'link' },
{ key: 'job', title: '职位', type: 'text' },
{ key: 'area', title: '地区', type: 'text' },
{
key: 'createdTime',
title: '创建时间',
displayValue: "{{dayjs($listItem.createdTime).format('LL')}}",
},
],
majorKey: 'id',
rowsPerPage: '3',
isMultiSelect: 'false',
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'tabContentVStack', slot: 'content' } },
},
],
},
{
id: 'userInfoContainer',
type: 'chakra_ui/v1/vstack',
properties: { spacing: '2', align: 'stretch' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'tabContentVStack', slot: 'content' } },
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: "{{!usersTable.selectedItem ? 'display: none' : ''}}",
},
},
],
},
{
id: 'userInfoTitle',
type: 'core/v1/text',
properties: { value: { raw: '**基本信息**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
],
},
{
id: 'hstack1',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
},
],
},
{
id: 'usernameLabel',
type: 'core/v1/text',
properties: { value: { raw: '**用户名**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack1', slot: 'content' } },
},
],
},
{
id: 'usernameValue',
type: 'core/v1/text',
properties: {
value: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.username : ''}}",
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack1', slot: 'content' } },
},
],
},
{
id: 'divider1',
type: 'chakra_ui/v1/divider',
properties: {},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
],
},
{
id: 'jobLabel',
type: 'core/v1/text',
properties: { value: { raw: '**职位**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack2', slot: 'content' } },
},
],
},
{
id: 'hstack2',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
},
],
},
{
id: 'areaLabel',
type: 'core/v1/text',
properties: { value: { raw: '**地区**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack3', slot: 'content' } },
},
],
},
{
id: 'areaValue',
type: 'core/v1/text',
properties: {
value: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.area : ''}}",
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack3', slot: 'content' } },
},
],
},
{
id: 'divider2',
type: 'chakra_ui/v1/divider',
properties: {},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
],
},
{
id: 'createdTimeLabel',
type: 'core/v1/text',
properties: { value: { raw: '**创建时间**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack4', slot: 'content' } },
},
],
},
{
id: 'createdTimeValue',
type: 'core/v1/text',
properties: {
value: {
raw: "{{usersTable.selectedItem ? dayjs(usersTable.selectedItem.createdTime).format('LL') : ''}}",
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack4', slot: 'content' } },
},
{ type: 'core/v1/style', properties: { string: { kind: {}, type: {} } } },
],
},
{
id: 'hstack3',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
},
],
},
{
id: 'divider3',
type: 'chakra_ui/v1/divider',
properties: {},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
],
},
{
id: 'hstack4',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'userInfoContainer', slot: 'content' } },
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
},
],
},
{
id: 'tabs1',
type: 'chakra_ui/v1/tabs',
properties: { tabNames: ['用户信息', '角色'], initialSelectedTabIndex: 0 },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'grid_layout1', slot: 'content' } },
},
],
},
{
id: 'tabContentVStack',
type: 'chakra_ui/v1/vstack',
properties: { spacing: '24px' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'tabs1', slot: 'content' } },
},
],
},
{
id: 'testtext',
type: 'core/v1/text',
properties: { value: { raw: '**测试角色**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'tabs1', slot: 'content' } },
},
],
},
{
id: 'jobValue',
type: 'chakra_ui/v1/vstack',
properties: { spacing: '1', align: 'stretch' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'hstack2', slot: 'content' } },
},
{
type: 'core/v1/style',
properties: { styleSlot: 'content', style: 'padding: 0; border: none' },
},
],
},
{
id: 'link1',
type: 'chakra_ui/v1/link',
properties: {
text: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.job : ''}}",
format: 'plain',
},
href: 'https://www.google.com',
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'jobValue', slot: 'content' } },
},
],
},
{
id: 'link2',
type: 'chakra_ui/v1/link',
properties: {
text: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.job : ''}}",
format: 'plain',
},
href: 'https://www.google.com',
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'jobValue', slot: 'content' } },
},
],
},
{
id: 'grid_layout2',
type: 'core/v1/grid_layout',
properties: {
layout: [
{ i: 'text10', x: 0, y: 0, w: 0, h: 0 },
{ i: 'form1', x: 0, y: 0, w: 0, h: 0 },
],
},
traits: [],
},
{
id: 'form1',
type: 'chakra_ui/v1/form',
properties: { hideSubmit: false },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'grid_layout2', slot: 'content' } },
},
],
},
{
id: 'button1',
type: 'chakra_ui/v1/button',
properties: {
text: { raw: 'text', format: 'plain' },
colorScheme: 'blue',
isLoading: false,
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'form1', slot: 'content' } },
},
],
},
{
id: 'button2',
type: 'chakra_ui/v1/button',
properties: {
text: { raw: 'text', format: 'plain' },
colorScheme: 'blue',
isLoading: false,
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'form1', slot: 'content' } },
},
],
},
{
id: 'button3',
type: 'chakra_ui/v1/button',
properties: {
text: { raw: 'text', format: 'plain' },
colorScheme: 'blue',
isLoading: false,
},
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'form1', slot: 'content' } },
},
],
},
],
},
},
};

View File

@ -0,0 +1,168 @@
import { Application } from '@sunmao-ui/core';
import { ApplicationFixture } from '../../__fixture__/application';
import { AdjustComponentOrderLeafOperation } from '../../src/operations/leaf/component/adjustComponentOrderLeafOperation';
describe('adjust component order operation', () => {
let app: Application;
let operation: AdjustComponentOrderLeafOperation;
let warnSpy: jest.SpyInstance;
beforeAll(() => {
warnSpy = jest.spyOn(console, 'warn');
});
describe('move up top level component', () => {
const stack: Application[] = [];
beforeAll(() => {
app = ApplicationFixture['adjustOrderOperation'];
stack[0] = app;
operation = new AdjustComponentOrderLeafOperation({
componentId: 'grid_layout2',
orientation: 'up',
});
});
it('should do operation', () => {
stack[1] = operation.do(stack[0]);
expect(stack[1].spec.components).toMatchSnapshot();
});
it('should undo operation', () => {
stack[2] = operation.undo(stack[1]);
expect(stack[2].spec.components).toEqual(stack[0].spec.components);
});
it('should redo operation', () => {
stack[3] = operation.redo(stack[2]);
expect(stack[3].spec.components).toEqual(stack[1].spec.components);
});
afterAll(() => {
app = undefined;
operation = undefined;
});
});
describe('move down top level component', () => {
const stack: Application[] = [];
beforeAll(() => {
app = ApplicationFixture['adjustOrderOperation'];
stack[0] = app;
operation = new AdjustComponentOrderLeafOperation({
componentId: 'grid_layout1',
orientation: 'down',
});
});
it('should do operation', () => {
stack[1] = operation.do(stack[0]);
expect(stack[1].spec.components).toMatchSnapshot();
});
it('should undo operation', () => {
stack[2] = operation.undo(stack[1]);
expect(stack[2].spec.components).toEqual(stack[0].spec.components);
});
it('should redo operation', () => {
stack[3] = operation.redo(stack[2]);
expect(stack[3].spec.components).toEqual(stack[1].spec.components);
});
afterAll(() => {
app = undefined;
operation = undefined;
});
});
it('should not move up top level component in the top', () => {
app = ApplicationFixture['adjustOrderOperation'];
operation = new AdjustComponentOrderLeafOperation({
componentId: 'grid_layout1',
orientation: 'up',
});
expect(operation.do(app)).toEqual(app);
expect(warnSpy).toHaveBeenCalledWith('the element cannot move up');
});
it('should not move down top level component in the bottom', () => {
app = ApplicationFixture['adjustOrderOperation'];
operation = new AdjustComponentOrderLeafOperation({
componentId: 'grid_layout2',
orientation: 'down',
});
expect(operation.do(app)).toEqual(app);
expect(warnSpy).toHaveBeenCalledWith('the element cannot move down');
});
describe('move up child component', () => {
const stack: Application[] = [];
beforeAll(() => {
app = ApplicationFixture['adjustOrderOperation'];
stack[0] = app;
operation = new AdjustComponentOrderLeafOperation({
componentId: 'userInfoContainer',
orientation: 'up',
});
});
it('should do operation', () => {
stack[1] = operation.do(stack[0]);
expect(stack[1].spec.components).toMatchSnapshot();
});
it('should undo operation', () => {
stack[2] = operation.undo(stack[1]);
expect(stack[2].spec.components).toEqual(stack[0].spec.components);
});
it('should redo operation', () => {
stack[3] = operation.redo(stack[2]);
expect(stack[3].spec.components).toEqual(stack[1].spec.components);
});
afterAll(() => {
app = undefined;
operation = undefined;
});
});
describe('move down child component', () => {
const stack: Application[] = [];
beforeAll(() => {
app = ApplicationFixture['adjustOrderOperation'];
stack[0] = app;
operation = new AdjustComponentOrderLeafOperation({
componentId: 'usersTable',
orientation: 'down',
});
});
it('should do operation', () => {
stack[1] = operation.do(stack[0]);
expect(stack[1].spec.components).toMatchSnapshot();
});
it('should undo operation', () => {
stack[2] = operation.undo(stack[1]);
expect(stack[2].spec.components).toEqual(stack[0].spec.components);
});
it('should redo operation', () => {
stack[3] = operation.redo(stack[2]);
expect(stack[3].spec.components).toEqual(stack[1].spec.components);
});
afterAll(() => {
app = undefined;
operation = undefined;
});
});
it('should not move up child component in the top', () => {
app = ApplicationFixture['adjustOrderOperation'];
operation = new AdjustComponentOrderLeafOperation({
componentId: 'usersTable',
orientation: 'up',
});
expect(operation.do(app)).toEqual(app);
expect(warnSpy).toHaveBeenCalledWith('the element cannot move up');
});
it('should not move down child component in the bottom', () => {
app = ApplicationFixture['adjustOrderOperation'];
operation = new AdjustComponentOrderLeafOperation({
componentId: 'userInfoContainer',
orientation: 'down',
});
expect(operation.do(app)).toEqual(app);
expect(warnSpy).toHaveBeenCalledWith('the element cannot move down');
});
beforeAll(() => {
warnSpy.mockClear();
});
});

View File

@ -27,7 +27,10 @@ export class AdjustComponentOrderLeafOperation extends BaseLeafOperation<AdjustC
case 'up':
for (this.dest = this.index - 1; this.dest >= 0; this.dest--) {
const nextComponent = draft.spec.components[this.dest];
if (!nextComponent.traits.some(t => t.type === 'core/v1/slot')) {
if (
nextComponent.type !== 'core/v1/dummy' &&
!nextComponent.traits.some(t => t.type === 'core/v1/slot')
) {
break;
}
}
@ -98,53 +101,46 @@ export class AdjustComponentOrderLeafOperation extends BaseLeafOperation<AdjustC
console.warn(`the element cannot move ${this.context.orientation}`);
return;
}
if (movedElement) {
switch (this.context.orientation) {
case 'up': {
const movedElement = draft.spec.components.splice(this.index, 1)[0];
draft.spec.components.splice(this.dest - 1, 0, movedElement);
break;
}
case 'down': {
const movedElement = draft.spec.components.splice(this.index, 1)[0];
draft.spec.components.splice(this.dest + 1, 0, movedElement);
break;
}
}
}
const [highPos, lowPos] = [this.dest, this.index].sort();
const lowComponent = draft.spec.components.splice(lowPos, 1)[0];
const highComponent = draft.spec.components.splice(highPos, 1)[0];
draft.spec.components.splice(lowPos - 1, 0, highComponent);
draft.spec.components.splice(highPos, 0, lowComponent);
});
}
redo(prev: Application): Application {
return produce(prev, draft => {
switch (this.context.orientation) {
case 'up': {
const movedElement = draft.spec.components.splice(this.index, 1)[0];
draft.spec.components.splice(this.dest - 1, 0, movedElement);
break;
}
case 'down': {
const movedElement = draft.spec.components.splice(this.index, 1)[0];
draft.spec.components.splice(this.dest + 1, 0, movedElement);
break;
}
if (this.index === -1) {
console.warn("operation hasn't been executed, cannot redo");
}
if (this.dest === -1) {
console.warn('cannot redo, the operation was failed executing');
return;
}
const lowPos = Math.max(this.dest, this.index);
const highPos = Math.min(this.dest, this.index);
const lowComponent = draft.spec.components.splice(lowPos, 1)[0];
const highComponent = draft.spec.components.splice(highPos, 1)[0];
draft.spec.components.splice(lowPos - 1, 0, highComponent);
draft.spec.components.splice(highPos, 0, lowComponent);
});
}
undo(prev: Application): Application {
return produce(prev, draft => {
switch (this.context.orientation) {
case 'up': {
const movedElement = draft.spec.components.splice(this.dest - 1, 1)[0];
draft.spec.components.splice(this.index, 0, movedElement);
break;
}
case 'down': {
const movedElement = draft.spec.components.splice(this.dest + 1, 1)[0];
draft.spec.components.splice(this.index, 0, movedElement);
break;
}
if (this.index === -1) {
console.warn("cannot undo operation, the operation hasn't been executed.");
}
if (this.dest === -1) {
console.warn('cannot undo, the operation was failed executing');
return;
}
const lowPos = Math.max(this.dest, this.index);
const highPos = Math.min(this.dest, this.index);
const lowComponent = draft.spec.components.splice(lowPos, 1)[0];
const highComponent = draft.spec.components.splice(highPos, 1)[0];
draft.spec.components.splice(lowPos - 1, 0, highComponent);
draft.spec.components.splice(highPos, 0, lowComponent);
});
}
}