From 4c9b91c4a13113ce17c120b3271e351463d8c782 Mon Sep 17 00:00:00 2001 From: xiaowei Date: Fri, 11 Dec 2020 14:24:55 +0800 Subject: [PATCH] feat: add basic statusBar --- src/extensions/index.ts | 3 + src/extensions/statusBar/index.tsx | 33 +++++++++ src/model/workbench/statusBar.ts | 45 ++++++++---- src/services/workbench/statusBarService.ts | 81 ++++++++++++++++------ src/workbench/statusBar/item.tsx | 28 ++++++++ src/workbench/statusBar/statusBar.tsx | 35 ++++++++-- src/workbench/statusBar/style.scss | 49 +++++++++++++ 7 files changed, 234 insertions(+), 40 deletions(-) create mode 100644 src/extensions/statusBar/index.tsx create mode 100644 src/workbench/statusBar/item.tsx diff --git a/src/extensions/index.ts b/src/extensions/index.ts index c99d137d1..4f677c198 100644 --- a/src/extensions/index.ts +++ b/src/extensions/index.ts @@ -1,6 +1,8 @@ import { ExtendActivityBar } from './activityBar'; import { ExtendExplore } from './explore'; import { ExtendSearch } from './search'; +import { ExtendStatusBar } from './statusBar'; + const Themes = require('./theme-defaults/package.json'); /** @@ -10,5 +12,6 @@ export const defaultExtensions = [ ExtendActivityBar, ExtendExplore, ExtendSearch, + ExtendStatusBar, Themes, ]; diff --git a/src/extensions/statusBar/index.tsx b/src/extensions/statusBar/index.tsx new file mode 100644 index 000000000..bd992332f --- /dev/null +++ b/src/extensions/statusBar/index.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { IExtensionService, IStatusBarItem } from 'mo'; +import { IExtension } from 'mo/model/extension'; +import { statusBarService } from 'mo/services'; +import { Icon } from 'mo/components/icon'; + +function init() { + const problems: IStatusBarItem = { + id: 'MoProblems', + sortIndex: 1, + name: 'Problems', + }; + + const notifications: IStatusBarItem = { + id: 'MoNotification', + sortIndex: 1, + name: 'Notification', + render: () => , + }; + + statusBarService.appendLeftItem(problems); + statusBarService.appendRightItem(notifications); + + statusBarService.onClick(function (e, item) { + console.log('statusBarService:', e, item); + }); +} + +export const ExtendStatusBar: IExtension = { + activate(extensionCtx: IExtensionService) { + init(); + }, +}; diff --git a/src/model/workbench/statusBar.ts b/src/model/workbench/statusBar.ts index 98f304e48..c1ec007da 100644 --- a/src/model/workbench/statusBar.ts +++ b/src/model/workbench/statusBar.ts @@ -1,28 +1,45 @@ +import { EventBus } from 'mo/common/event'; import { observable } from 'mo/common/observable'; -import { container, inject, injectable } from 'tsyringe'; +import * as React from 'react'; +import { injectable } from 'tsyringe'; -export interface IStatusBarItem {} +export interface IStatusBarItem extends HTMLElementProps { + id: string; + sortIndex: number; + onClick?(e: React.MouseEvent, item?: IStatusBarItem); + render?: () => ReactNode; + name?: string; +} export interface IStatusBar { - data: IStatusBarItem[]; - onClick: (event: React.MouseEvent) => void; - render?: () => React.ReactNode | JSX.Element; + rightItems: IStatusBarItem[]; + leftItems: IStatusBarItem[]; + onClick?: (e: React.MouseEvent, item: IStatusBarItem) => void; +} + +/** + * The activity bar event definition + */ +export enum StatusBarEvent { + /** + * Selected an activity bar + */ + onClick = 'statusBar.onClick', + /** + * Activity bar data changed + */ + DataChanged = 'statusBar.data', } @observable() @injectable() export class StatusBarModel implements IStatusBar { - public data: IStatusBarItem[] = []; - - constructor(@inject('StatusBarData') data: IStatusBarItem[] = []) { - this.data = data; - } + public leftItems: IStatusBarItem[] = []; + public rightItems: IStatusBarItem[] = []; public render!: () => React.ReactNode; - public onClick = (event: React.MouseEvent) => { - console.log('onClick:', event); + public onClick = (e: React.MouseEvent, item: IStatusBarItem) => { + EventBus.emit(StatusBarEvent.onClick, e, item); }; } - -container.register('StatusBarData', { useValue: [] }); diff --git a/src/services/workbench/statusBarService.ts b/src/services/workbench/statusBarService.ts index 840556212..99eca8c6d 100644 --- a/src/services/workbench/statusBarService.ts +++ b/src/services/workbench/statusBarService.ts @@ -1,31 +1,39 @@ import { IStatusBar, IStatusBarItem, + StatusBarEvent, StatusBarModel, } from 'mo/model/workbench/statusBar'; import { Component } from 'mo/react'; -import { emit } from 'mo/common/event'; import { container, singleton } from 'tsyringe'; -/** - * The activity bar event definition - */ -export enum StatusBarEvent { +export interface IStatusBarService extends Component { + appendLeftItem(item: IStatusBarItem): void; + appendRightItem(item: IStatusBarItem): void; + updateItem(item: IStatusBarItem): void; + findById(id: string): IStatusBarItem | null; + /** + * Remove the left item of StatusBar, + * return the removed item. + * @param id + */ + removeLeftItem(id: string): IStatusBarItem; /** - * Selected an activity bar + * Remove the right item of StatusBar, + * return the removed item. + * @param id */ - onClick = 'statusBar.onClick', + removeRightItem(id: string): IStatusBarItem; /** - * Activity bar data changed + * Listen to the statusbar onclick event + * @param callback */ - DataChanged = 'statusBar.data', + onClick(callback: (e: MouseEvent, item: IStatusBarItem) => void); } -export interface IStatusBarService extends Component { - push(data: IStatusBarItem | IStatusBarItem[]): void; - remove(index: number): void; +function searchById(id: string) { + return (item: IStatusBarItem) => item.id === id; } - @singleton() export class StatusBarService extends Component @@ -37,17 +45,46 @@ export class StatusBarService this.state = container.resolve(StatusBarModel); } - @emit(StatusBarEvent.DataChanged) - public push(data: IStatusBarItem | IStatusBarItem[]) { - let original = this.state.data; - if (Array.isArray(data)) { - original = original.concat(data); - } else { - original.push(data); + onClick(callback: (e: MouseEvent, item: IStatusBarItem) => void) { + this.subscribe(StatusBarEvent.onClick, callback); + } + + private remove(id: string, arr: IStatusBarItem[]): IStatusBarItem { + const index = arr.findIndex(searchById(id)); + const result = arr.splice(index, 1); + return result[0]; + } + + removeLeftItem(id: string): IStatusBarItem { + return this.remove(id, this.state.leftItems); + } + + removeRightItem(id: string): IStatusBarItem { + return this.remove(id, this.state.rightItems); + } + + findById(id: string): IStatusBarItem { + let result; + const { leftItems, rightItems } = this.state; + result = leftItems.find(searchById(id)); + if (!result) { + result = rightItems.find(searchById(id)); } + return result; } - public remove(index: number) { - this.state.data.splice(index, 1); + appendLeftItem(item: IStatusBarItem): void { + this.state.leftItems.push(item); + } + + appendRightItem(item: IStatusBarItem): void { + this.state.rightItems.push(item); + } + + updateItem(item: IStatusBarItem): void { + const original = this.findById(item.id); + if (original) { + Object.assign(original, item); + } } } diff --git a/src/workbench/statusBar/item.tsx b/src/workbench/statusBar/item.tsx new file mode 100644 index 000000000..139311ae2 --- /dev/null +++ b/src/workbench/statusBar/item.tsx @@ -0,0 +1,28 @@ +import { classNames, getBEMElement } from 'mo/common/className'; +import { IStatusBarItem } from 'mo/model/workbench/statusBar'; +import * as React from 'react'; +import { memo } from 'react'; +import { statusBarClassName } from './statusBar'; + +function StatusItem(props: IStatusBarItem) { + const itemClassName = getBEMElement(statusBarClassName, 'item'); + + const { className, onClick, name, render, ...extra } = props; + + const clsName = classNames(itemClassName, className); + const events = { + onClick: function (e: React.MouseEvent) { + onClick?.(e, props); + }, + }; + + return ( + + ); +} + +export default memo(StatusItem); diff --git a/src/workbench/statusBar/statusBar.tsx b/src/workbench/statusBar/statusBar.tsx index f72e77803..a304b79a8 100644 --- a/src/workbench/statusBar/statusBar.tsx +++ b/src/workbench/statusBar/statusBar.tsx @@ -3,13 +3,40 @@ import 'mo/workbench/statusBar/style.scss'; import * as React from 'react'; import { memo } from 'react'; -import { prefixClaName } from 'mo/common/className'; -import { IStatusBar } from 'mo/model/workbench/statusBar'; +import { getBEMElement, prefixClaName } from 'mo/common/className'; +import { IStatusBar, IStatusBarItem } from 'mo/model/workbench/statusBar'; +import StatusItem from './item'; +import { mergeFunctions } from 'mo/common/utils'; -const defaultClassName = prefixClaName('statusBar'); +export const statusBarClassName = prefixClaName('statusBar'); +const leftItemsClassName = getBEMElement(statusBarClassName, 'left-items'); +const rightItemsClassName = getBEMElement(statusBarClassName, 'right-items'); + +function sortByIndex(a: IStatusBarItem, b: IStatusBarItem) { + return a.sortIndex - b.sortIndex; +} function StatusBar(props: IStatusBar) { - return
StatusBar
; + const { leftItems = [], onClick, rightItems = [] } = props; + + const renderItems = (data: IStatusBarItem[]) => { + return data + .sort(sortByIndex) + .map((item: IStatusBarItem) => ( + + )); + }; + + return ( +
+
{renderItems(leftItems)}
+
{renderItems(rightItems)}
+
+ ); } export default memo(StatusBar); diff --git a/src/workbench/statusBar/style.scss b/src/workbench/statusBar/style.scss index d32772e67..a2774a0d3 100644 --- a/src/workbench/statusBar/style.scss +++ b/src/workbench/statusBar/style.scss @@ -1,10 +1,59 @@ @import 'mo/style/common'; #{$statusBar} { + align-items: center; bottom: 0; + display: flex; height: 22px; + justify-content: center; position: absolute; text-align: center; width: 100%; z-index: 1; + + &__left-items, + &__right-items { + display: flex; + height: 100%; + } + + &__left-items { + flex-grow: 1; + + > :first-child { + margin-left: 7px; + } + } + + &__right-items { + direction: rtl; + + > :last-child { + margin-right: 7px; + } + } + + &__item { + height: 100%; + + a { + align-items: center; + cursor: pointer; + display: flex; + height: 100%; + outline-width: 0; + overflow: hidden; + padding: 0 5px; + text-overflow: ellipsis; + white-space: pre; + } + + &:hover { + background-color: rgba(255, 255, 255, 0.12); + } + + .codicon { + font-size: 14px; + } + } }