Skip to content

Commit

Permalink
feat(panel): support add, remove and update Panel features, builtin O…
Browse files Browse the repository at this point in the history
…UTPUT and PROBLEMS panel

Add basic Panel view, and suppurted add, remove, get, update and so on operations for Panel.

re #19, re #22
  • Loading branch information
wewoor committed Feb 25, 2021
1 parent abe61d6 commit 909bcc8
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 54 deletions.
16 changes: 0 additions & 16 deletions src/controller/panel.ts

This file was deleted.

42 changes: 42 additions & 0 deletions src/controller/panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import { IActionBarItem } from 'mo/components/actionBar';
import { PanelStatus } from 'mo/model/workbench/panel';
import { Controller } from 'mo/react/controller';
import { panelService } from 'mo/services';
import { singleton } from 'tsyringe';

export interface IPanelController {
onTabChange(key: string | undefined): void;
onToolbarClick(e: React.MouseEvent, item: IActionBarItem): void;
}

@singleton()
export class PanelController extends Controller implements IPanelController {
constructor() {
super();
}

public readonly onTabChange = (key: string): void => {
const state = panelService.getState();
if (key) {
panelService.setState({
current: state.data?.find((item) => item.id === key),
});
}
};

public readonly onToolbarClick = (
e: React.MouseEvent,
item: IActionBarItem
): void => {
if (item.id === 'Closeable') {
panelService.setState({
status: PanelStatus.Close,
});
} else if (item.id === 'Resize') {
panelService.setState({
status: PanelStatus.Maximize,
});
}
};
}
33 changes: 0 additions & 33 deletions src/model/workbench/panel.ts

This file was deleted.

70 changes: 70 additions & 0 deletions src/model/workbench/panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react';
import { IActionBarItem } from 'mo/components/actionBar';
import { ITab } from 'mo/components/tabs/tab';
import { injectable } from 'tsyringe';
import Output from 'mo/workbench/panel/output';
import Problems from 'mo/workbench/panel/problems';

export interface IPanelItem<T = any> extends ITab<any> {
id: string;
title?: string;
toolbox?: IActionBarItem[];
data?: T;
render?(item: IPanelItem): ReactNode;
}

export enum PanelEvent {
onClick = 'panel.onClick',
}

export enum PanelStatus {
Close = 'close',
Open = 'open',
Maximize = 'maximize',
}

export interface IPanel {
current?: IPanelItem;
data?: IPanelItem[];
toolbox?: IActionBarItem[];
status?: PanelStatus;
}

export const PANEL_PROBLEMS: IPanelItem = {
id: 'ProblemsPane',
name: 'problems',
renderPanel: 'Problems',
data: null,
render: (item) => <Problems {...item} />,
};

export const PANEL_OUTPUT: IPanelItem = {
id: 'OutputPane',
name: 'output',
renderPanel: 'output',
data: 'output',
render: (item) => <Output {...item} />,
};

export const PANEL_TOOLBOX_CLOSE = {
id: 'Close',
title: 'Close Panel',
iconName: 'codicon-close',
};

export const PANEL_TOOLBOX_RESIZE = {
id: 'Resize',
title: 'Maximize Panel Size',
iconName: 'codicon-chevron-up',
};

@injectable()
export class PanelModel implements IPanel {
public current: IPanelItem | undefined = PANEL_OUTPUT;
public data: IPanelItem[] = ([] = [PANEL_PROBLEMS, PANEL_OUTPUT]);
public status = PanelStatus.Open;
public toolbox: IActionBarItem[] = [
PANEL_TOOLBOX_RESIZE,
PANEL_TOOLBOX_CLOSE,
];
}
98 changes: 96 additions & 2 deletions src/services/workbench/panelService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { IPanel, PanelModel } from 'mo/model/workbench/panel';
import { Component } from 'mo/react';
import { singleton, container } from 'tsyringe';
import {
IPanel,
IPanelItem,
PanelModel,
PANEL_OUTPUT,
PANEL_PROBLEMS,
} from 'mo/model/workbench/panel';

export interface IPanelService extends Component<IPanel> {}
import { searchById } from '../helper';
export interface IPanelService extends Component<IPanel> {
open(data: IPanelItem): void;
getById(id: string): IPanelItem | undefined;
add(data: IPanelItem | IPanelItem[]): void;
update(data: IPanelItem): IPanelItem | undefined;
remove(id: string): IPanelItem | undefined;
appendOutput(content: string): void;
updateOutput(data: IPanelItem): IPanelItem | undefined;
clearOutput(): void;
updateProblems(data: IPanelItem): IPanelItem | undefined;
clearProblems(): void;
}

@singleton()
export class PanelService extends Component<IPanel> implements IPanelService {
Expand All @@ -12,4 +30,80 @@ export class PanelService extends Component<IPanel> implements IPanelService {
super();
this.state = container.resolve(PanelModel);
}
public open(data: IPanelItem<any>): void {
let current = this.getById(data.id);
if (!current) {
this.add(data);
current = data;
}
this.setState({
current: current,
});
}

public getById(id: string): IPanelItem<any> | undefined {
const { data = [] } = this.state;
return data.find(searchById(id));
}

public updateOutput(data: IPanelItem<any>): IPanelItem | undefined {
return this.update(Object.assign(PANEL_OUTPUT, data));
}

public updateProblems(data: IPanelItem<any>): IPanelItem | undefined {
return this.update(Object.assign(PANEL_PROBLEMS, data));
}

public clearProblems(): void {
this.updateOutput(Object.assign(PANEL_PROBLEMS, { data: null }));
}

public appendOutput(content: string): void {
const output = this.getById(PANEL_OUTPUT.id);
if (output) {
output.data = output.data + content;
this.updateOutput(output);
}
}

public clearOutput(): void {
this.updateOutput(Object.assign(PANEL_OUTPUT, { data: '' }));
}

public add(data: IPanelItem | IPanelItem[]) {
let original = this.state.data || [];
if (Array.isArray(data)) {
original = original.concat(data);
} else {
original.push(data);
}
this.setState({
data: original,
});
}

public update(data: IPanelItem): IPanelItem | undefined {
const panes = this.state.data || [];
const targetIndex = panes?.findIndex(searchById(data.id));
if (targetIndex !== undefined && targetIndex > -1) {
Object.assign(panes[targetIndex], data);
this.render();
return panes[targetIndex];
}
return undefined;
}

public remove(id: string): IPanelItem | undefined {
const { data } = this.state;

const targetIndex = data?.findIndex(searchById(id));
if (targetIndex !== undefined && targetIndex > -1) {
const result = data?.splice(targetIndex, 1) || [];
this.setState({
data: data,
});
return result[0];
}
return undefined;
}
}
26 changes: 26 additions & 0 deletions src/workbench/panel/output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';
import { memo } from 'react';
import { prefixClaName } from 'mo/common/className';
import { IPanelItem } from 'mo/model/workbench/panel';
import MonacoEditor from 'mo/components/monaco';

const defaultClassName = prefixClaName('output');

function Output(props: IPanelItem) {
const { data } = props;
return (
<div className={defaultClassName}>
<MonacoEditor
options={{
value: data,
readOnly: true,
lineDecorationsWidth: 0,
lineNumbers: 'off',
automaticLayout: true,
}}
/>
</div>
);
}

export default memo(Output);
34 changes: 31 additions & 3 deletions src/workbench/panel/panel.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
import * as React from 'react';
import { memo } from 'react';
import { prefixClaName } from 'mo/common/className';
import { getBEMElement, prefixClaName } from 'mo/common/className';
import { IPanel } from 'mo/model/workbench/panel';
import { IPanelController } from 'mo/controller/panel';
import { Tabs } from 'mo/components/tabs';
import ActionBar from 'mo/components/actionBar';

const defaultClassName = prefixClaName('panel');
const panelHeaderClassName = getBEMElement(defaultClassName, 'header');

function Panel(props) {
const panelToolbarClassName = getBEMElement(defaultClassName, 'toolbar');

const panelContainerClassName = getBEMElement(defaultClassName, 'container');

function Panel(props: IPanel & IPanelController) {
console.log('Panel render:', props);
const { data, current, toolbox = [], onTabChange, onToolbarClick } = props;
let toolboxData = toolbox;
if (current && current.toolbox) {
toolboxData = current.toolbox.concat(toolbox);
}

return <div className={defaultClassName}>Panel</div>;
return (
<div className={defaultClassName}>
<div className={panelHeaderClassName}>
<Tabs data={data} onSelectTab={onTabChange} />
<ActionBar
className={panelToolbarClassName}
data={toolboxData || []}
onClick={onToolbarClick}
/>
</div>
<div className={panelContainerClassName}>
{current?.render?.(current)}
</div>
</div>
);
}

export default memo(Panel);
17 changes: 17 additions & 0 deletions src/workbench/panel/problems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';
import { memo } from 'react';
import { prefixClaName } from 'mo/common/className';
import { IPanelItem } from 'mo/model/workbench/panel';

const defaultClassName = prefixClaName('problems');

function Problems(props: IPanelItem) {
const { data } = props;
return (
<div className={defaultClassName} style={{ margin: '0 18px' }}>
Problems {data}
</div>
);
}

export default memo(Problems);
Loading

0 comments on commit 909bcc8

Please sign in to comment.