Skip to content

Commit 909bcc8

Browse files
committed
feat(panel): support add, remove and update Panel features, builtin OUTPUT and PROBLEMS panel
Add basic Panel view, and suppurted add, remove, get, update and so on operations for Panel. re #19, re #22
1 parent abe61d6 commit 909bcc8

File tree

9 files changed

+322
-54
lines changed

9 files changed

+322
-54
lines changed

src/controller/panel.ts

-16
This file was deleted.

src/controller/panel.tsx

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as React from 'react';
2+
import { IActionBarItem } from 'mo/components/actionBar';
3+
import { PanelStatus } from 'mo/model/workbench/panel';
4+
import { Controller } from 'mo/react/controller';
5+
import { panelService } from 'mo/services';
6+
import { singleton } from 'tsyringe';
7+
8+
export interface IPanelController {
9+
onTabChange(key: string | undefined): void;
10+
onToolbarClick(e: React.MouseEvent, item: IActionBarItem): void;
11+
}
12+
13+
@singleton()
14+
export class PanelController extends Controller implements IPanelController {
15+
constructor() {
16+
super();
17+
}
18+
19+
public readonly onTabChange = (key: string): void => {
20+
const state = panelService.getState();
21+
if (key) {
22+
panelService.setState({
23+
current: state.data?.find((item) => item.id === key),
24+
});
25+
}
26+
};
27+
28+
public readonly onToolbarClick = (
29+
e: React.MouseEvent,
30+
item: IActionBarItem
31+
): void => {
32+
if (item.id === 'Closeable') {
33+
panelService.setState({
34+
status: PanelStatus.Close,
35+
});
36+
} else if (item.id === 'Resize') {
37+
panelService.setState({
38+
status: PanelStatus.Maximize,
39+
});
40+
}
41+
};
42+
}

src/model/workbench/panel.ts

-33
This file was deleted.

src/model/workbench/panel.tsx

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as React from 'react';
2+
import { IActionBarItem } from 'mo/components/actionBar';
3+
import { ITab } from 'mo/components/tabs/tab';
4+
import { injectable } from 'tsyringe';
5+
import Output from 'mo/workbench/panel/output';
6+
import Problems from 'mo/workbench/panel/problems';
7+
8+
export interface IPanelItem<T = any> extends ITab<any> {
9+
id: string;
10+
title?: string;
11+
toolbox?: IActionBarItem[];
12+
data?: T;
13+
render?(item: IPanelItem): ReactNode;
14+
}
15+
16+
export enum PanelEvent {
17+
onClick = 'panel.onClick',
18+
}
19+
20+
export enum PanelStatus {
21+
Close = 'close',
22+
Open = 'open',
23+
Maximize = 'maximize',
24+
}
25+
26+
export interface IPanel {
27+
current?: IPanelItem;
28+
data?: IPanelItem[];
29+
toolbox?: IActionBarItem[];
30+
status?: PanelStatus;
31+
}
32+
33+
export const PANEL_PROBLEMS: IPanelItem = {
34+
id: 'ProblemsPane',
35+
name: 'problems',
36+
renderPanel: 'Problems',
37+
data: null,
38+
render: (item) => <Problems {...item} />,
39+
};
40+
41+
export const PANEL_OUTPUT: IPanelItem = {
42+
id: 'OutputPane',
43+
name: 'output',
44+
renderPanel: 'output',
45+
data: 'output',
46+
render: (item) => <Output {...item} />,
47+
};
48+
49+
export const PANEL_TOOLBOX_CLOSE = {
50+
id: 'Close',
51+
title: 'Close Panel',
52+
iconName: 'codicon-close',
53+
};
54+
55+
export const PANEL_TOOLBOX_RESIZE = {
56+
id: 'Resize',
57+
title: 'Maximize Panel Size',
58+
iconName: 'codicon-chevron-up',
59+
};
60+
61+
@injectable()
62+
export class PanelModel implements IPanel {
63+
public current: IPanelItem | undefined = PANEL_OUTPUT;
64+
public data: IPanelItem[] = ([] = [PANEL_PROBLEMS, PANEL_OUTPUT]);
65+
public status = PanelStatus.Open;
66+
public toolbox: IActionBarItem[] = [
67+
PANEL_TOOLBOX_RESIZE,
68+
PANEL_TOOLBOX_CLOSE,
69+
];
70+
}
+96-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
1-
import { IPanel, PanelModel } from 'mo/model/workbench/panel';
21
import { Component } from 'mo/react';
32
import { singleton, container } from 'tsyringe';
3+
import {
4+
IPanel,
5+
IPanelItem,
6+
PanelModel,
7+
PANEL_OUTPUT,
8+
PANEL_PROBLEMS,
9+
} from 'mo/model/workbench/panel';
410

5-
export interface IPanelService extends Component<IPanel> {}
11+
import { searchById } from '../helper';
12+
export interface IPanelService extends Component<IPanel> {
13+
open(data: IPanelItem): void;
14+
getById(id: string): IPanelItem | undefined;
15+
add(data: IPanelItem | IPanelItem[]): void;
16+
update(data: IPanelItem): IPanelItem | undefined;
17+
remove(id: string): IPanelItem | undefined;
18+
appendOutput(content: string): void;
19+
updateOutput(data: IPanelItem): IPanelItem | undefined;
20+
clearOutput(): void;
21+
updateProblems(data: IPanelItem): IPanelItem | undefined;
22+
clearProblems(): void;
23+
}
624

725
@singleton()
826
export class PanelService extends Component<IPanel> implements IPanelService {
@@ -12,4 +30,80 @@ export class PanelService extends Component<IPanel> implements IPanelService {
1230
super();
1331
this.state = container.resolve(PanelModel);
1432
}
33+
public open(data: IPanelItem<any>): void {
34+
let current = this.getById(data.id);
35+
if (!current) {
36+
this.add(data);
37+
current = data;
38+
}
39+
this.setState({
40+
current: current,
41+
});
42+
}
43+
44+
public getById(id: string): IPanelItem<any> | undefined {
45+
const { data = [] } = this.state;
46+
return data.find(searchById(id));
47+
}
48+
49+
public updateOutput(data: IPanelItem<any>): IPanelItem | undefined {
50+
return this.update(Object.assign(PANEL_OUTPUT, data));
51+
}
52+
53+
public updateProblems(data: IPanelItem<any>): IPanelItem | undefined {
54+
return this.update(Object.assign(PANEL_PROBLEMS, data));
55+
}
56+
57+
public clearProblems(): void {
58+
this.updateOutput(Object.assign(PANEL_PROBLEMS, { data: null }));
59+
}
60+
61+
public appendOutput(content: string): void {
62+
const output = this.getById(PANEL_OUTPUT.id);
63+
if (output) {
64+
output.data = output.data + content;
65+
this.updateOutput(output);
66+
}
67+
}
68+
69+
public clearOutput(): void {
70+
this.updateOutput(Object.assign(PANEL_OUTPUT, { data: '' }));
71+
}
72+
73+
public add(data: IPanelItem | IPanelItem[]) {
74+
let original = this.state.data || [];
75+
if (Array.isArray(data)) {
76+
original = original.concat(data);
77+
} else {
78+
original.push(data);
79+
}
80+
this.setState({
81+
data: original,
82+
});
83+
}
84+
85+
public update(data: IPanelItem): IPanelItem | undefined {
86+
const panes = this.state.data || [];
87+
const targetIndex = panes?.findIndex(searchById(data.id));
88+
if (targetIndex !== undefined && targetIndex > -1) {
89+
Object.assign(panes[targetIndex], data);
90+
this.render();
91+
return panes[targetIndex];
92+
}
93+
return undefined;
94+
}
95+
96+
public remove(id: string): IPanelItem | undefined {
97+
const { data } = this.state;
98+
99+
const targetIndex = data?.findIndex(searchById(id));
100+
if (targetIndex !== undefined && targetIndex > -1) {
101+
const result = data?.splice(targetIndex, 1) || [];
102+
this.setState({
103+
data: data,
104+
});
105+
return result[0];
106+
}
107+
return undefined;
108+
}
15109
}

src/workbench/panel/output.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react';
2+
import { memo } from 'react';
3+
import { prefixClaName } from 'mo/common/className';
4+
import { IPanelItem } from 'mo/model/workbench/panel';
5+
import MonacoEditor from 'mo/components/monaco';
6+
7+
const defaultClassName = prefixClaName('output');
8+
9+
function Output(props: IPanelItem) {
10+
const { data } = props;
11+
return (
12+
<div className={defaultClassName}>
13+
<MonacoEditor
14+
options={{
15+
value: data,
16+
readOnly: true,
17+
lineDecorationsWidth: 0,
18+
lineNumbers: 'off',
19+
automaticLayout: true,
20+
}}
21+
/>
22+
</div>
23+
);
24+
}
25+
26+
export default memo(Output);

src/workbench/panel/panel.tsx

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
import * as React from 'react';
22
import { memo } from 'react';
3-
import { prefixClaName } from 'mo/common/className';
3+
import { getBEMElement, prefixClaName } from 'mo/common/className';
4+
import { IPanel } from 'mo/model/workbench/panel';
5+
import { IPanelController } from 'mo/controller/panel';
6+
import { Tabs } from 'mo/components/tabs';
7+
import ActionBar from 'mo/components/actionBar';
48

59
const defaultClassName = prefixClaName('panel');
10+
const panelHeaderClassName = getBEMElement(defaultClassName, 'header');
611

7-
function Panel(props) {
12+
const panelToolbarClassName = getBEMElement(defaultClassName, 'toolbar');
13+
14+
const panelContainerClassName = getBEMElement(defaultClassName, 'container');
15+
16+
function Panel(props: IPanel & IPanelController) {
817
console.log('Panel render:', props);
18+
const { data, current, toolbox = [], onTabChange, onToolbarClick } = props;
19+
let toolboxData = toolbox;
20+
if (current && current.toolbox) {
21+
toolboxData = current.toolbox.concat(toolbox);
22+
}
923

10-
return <div className={defaultClassName}>Panel</div>;
24+
return (
25+
<div className={defaultClassName}>
26+
<div className={panelHeaderClassName}>
27+
<Tabs data={data} onSelectTab={onTabChange} />
28+
<ActionBar
29+
className={panelToolbarClassName}
30+
data={toolboxData || []}
31+
onClick={onToolbarClick}
32+
/>
33+
</div>
34+
<div className={panelContainerClassName}>
35+
{current?.render?.(current)}
36+
</div>
37+
</div>
38+
);
1139
}
1240

1341
export default memo(Panel);

src/workbench/panel/problems.tsx

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as React from 'react';
2+
import { memo } from 'react';
3+
import { prefixClaName } from 'mo/common/className';
4+
import { IPanelItem } from 'mo/model/workbench/panel';
5+
6+
const defaultClassName = prefixClaName('problems');
7+
8+
function Problems(props: IPanelItem) {
9+
const { data } = props;
10+
return (
11+
<div className={defaultClassName} style={{ margin: '0 18px' }}>
12+
Problems {data}
13+
</div>
14+
);
15+
}
16+
17+
export default memo(Problems);

0 commit comments

Comments
 (0)