diff --git a/.gitignore b/.gitignore index a2e3fd1b8..67a0bd24d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ website/build # Generated files website/.docusaurus website/.cache-loader +website/docs/api # Misc website/.DS_Store diff --git a/.prettierignore b/.prettierignore index d666ec07a..0e2030d0b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,4 +6,8 @@ node_modules yarn.lock coverage storybook-static -docs/api \ No newline at end of file +docs/api +website/build +website/docs/api +website/.docusaurus +website/node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb63c8a0..0d444475a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.9.0-beta.1](https://github.com/DTStack/molecule/compare/v0.9.0-alpha.6...v0.9.0-beta.1) (2021-12-06) + +### Features + +- support a new Welcome page ([#508](https://github.com/DTStack/molecule/issues/508)) ([e5e0a0c](https://github.com/DTStack/molecule/commit/e5e0a0c42ffc4b717b87f8e1d7ffa6259c557788)) +- support sort in folderTree ([#524](https://github.com/DTStack/molecule/issues/524)) ([8802b1f](https://github.com/DTStack/molecule/commit/8802b1f70ec85a597038f003ebf60feafb2ef127)) + +### Bug Fixes + +- adjust the menu icon size ([ed9cb8d](https://github.com/DTStack/molecule/commit/ed9cb8d7f50c0d75893593edaf83e7d7360812d5)) +- fix can't clear the notifications ([#518](https://github.com/DTStack/molecule/issues/518)) ([2503f4a](https://github.com/DTStack/molecule/commit/2503f4a5699700b56247a94a51f38d64dfbf4ba0)) +- fix create file node incorrect on contextMenu ([#522](https://github.com/DTStack/molecule/issues/522)) ([14c6057](https://github.com/DTStack/molecule/commit/14c6057b34e27cb69997f932867f4bebbf27a6eb)) +- improve the circular dep error when execute yarn link ([#528](https://github.com/DTStack/molecule/issues/528)) ([486182b](https://github.com/DTStack/molecule/commit/486182b1ef62f72f954edfc08f3edb84ababa62c)) +- improve the exports of models ([#507](https://github.com/DTStack/molecule/issues/507)) ([4b7ab03](https://github.com/DTStack/molecule/commit/4b7ab0331a0374567c4ad404413e49c40a0387aa)) +- improve the insert strategy in folderTree ([#486](https://github.com/DTStack/molecule/issues/486)) ([3061b68](https://github.com/DTStack/molecule/commit/3061b68ecea6887aacdf1db19ae56261a144a76d)) +- initView will override the setDefaultValue ([#513](https://github.com/DTStack/molecule/issues/513)) ([586335e](https://github.com/DTStack/molecule/commit/586335e2e5085f1fdc36c79861bf01c0346427c2)) +- remove the warning in console ([#529](https://github.com/DTStack/molecule/issues/529)) ([059016c](https://github.com/DTStack/molecule/commit/059016cb4ca19854c200ac0791a198eb521a2d3f)) +- show the SubMenu in right place when the Menu is horizontal mode ([#526](https://github.com/DTStack/molecule/issues/526)) ([0d76520](https://github.com/DTStack/molecule/commit/0d76520eb03076314ff2f5fda7f51ddfcf0cbdb9)) + ## [0.9.0-alpha.6](https://github.com/DTStack/molecule/compare/v0.9.0-alpha.5...v0.9.0-alpha.6) (2021-10-27) ### Features diff --git a/README-zhCN.md b/README-zhCN.md new file mode 100644 index 000000000..c8901f8fd --- /dev/null +++ b/README-zhCN.md @@ -0,0 +1,108 @@ +
+ + watchman-logo +

Molecule

+

一个轻量的 Web IDE UI 框架

+ +[![CI][ci-image]][ci-url] [![Codecov][codecov-image]][codecov-url] [![NPM downloads][download-img]][download-url] [![NPM version][npm-version]][npm-version-url] + +
+ +[ci-image]: https://github.com/DTStack/molecule/actions/workflows/main.yml/badge.svg +[ci-url]: https://github.com/DTStack/molecule/actions/workflows/main.yml +[codecov-image]: https://codecov.io/gh/DTStack/molecule/branch/main/graph/badge.svg?token=PDjbCBo6qz +[codecov-url]: https://codecov.io/gh/DTStack/molecule +[download-img]: https://img.shields.io/npm/dm/@dtinsight/molecule.svg?style=flat +[download-url]: https://www.npmjs.com/package/@dtinsight/molecule +[npm-version]: https://img.shields.io/npm/v/@dtinsight/molecule.svg?style=flat-square +[npm-version-url]: https://www.npmjs.com/package/@dtinsight/molecule + +[中文](./README-zhCN.md) | [English](./README.md) + +Molecule 是一款受 **VSCode** 启发,使用 **React.js** 构建的 **Web IDE UI** 框架。我们设计了类似 VSCode 的**扩展**(Extension)机制,可以帮助我们使用 React 组件快速完成对 Workbench 的自定义。Molecule 与 **React** 项目集成非常方便,我们已经在 [DTStack](https://www.dtstack.com/) 多个产品、项目中使用。 + +[在线预览](https://github.com/DTStack/molecule-examples) + +## 核心功能 + +- 内置 React 版本的 Visual Studio Code **Workbench** UI +- 基本兼容 Visual Studio Code 的 **ColorTheme** +- 支持使用 React 组件自定义 **Workbench** UI 样式 +- 内置 Monaco Editor **Command Palette**、**Keybinding**等模块,并支持扩展 +- 支持 **i18n**,内置简体中文、English 2 种语言 +- 内置一个简单的 **Settings** 模块,支持在线编辑修改以及扩展 +- 内置默认的 **Explorer**, **Search** 等组件,并支持扩展 +- Typescript 支持 + +## 安装 + +```bash +npm install @dtinsight/molecule +# Or +yarn add @dtinsight/molecule +``` + +## 基本使用 + +```javascript +import React from 'react'; +import ReactDOM from 'react-dom'; +import { MoleculeProvider, Workbench } from '@dtinsight/molecule'; +import '@dtinsight/molecule/esm/style/mo.css'; + +const App = () => ( + + + +); + +ReactDOM.render(, document.getElementById('root')); +``` + +`extension` 为 Workbench 应用的扩展入口,如何编写扩展,请参考[快速开始](./website/docs/guides/the-first-extension.md)。 + +## 文档 + +- [简介](./website/docs/introduction.md) +- [快速开始](./website/docs/introduction.md) +- [API 文档](./website/docs/api/index.md) +- [扩展 Workbench](./website/docs/guides/extends-workbench.md) +- [More Docs](./website/docs). +- [Examples](https://github.com/DTStack/molecule-examples) + +## 开发 + +````bash +git clone git@github.com:DTStack/molecule.git +`` +` +首先 Clone 源码到本地 +**开发模式** + +```bash +yarn # install dependencies + +yarn dev # 启动开发模式 +```` + +Molecule 中的组件是基于 Storybook 开发并管理的,预览地址:`http://localhost:6006/`默认地址浏览。 + +**构建 & 预览** + +```bash +yarn build +yarn web # 预览打包后的 Web +``` + +当前我们默认将 Molecule 以 `ES6` 模块的方式构建到 **`esm`** 目录。另外, +这里除了 Storybook 提供的组件预览模式以外,我们同时内置了一个使用 ESM 模块的 `Web` 预览模式。 + +## 贡献 + +更多请参考 [CONTRIBUTING](./CONTRIBUTING.md). + +## License + +Copyright © DTStack. All rights reserved. + +Licensed under the MIT license. diff --git a/README.md b/README.md index 530a1f3b3..8f7d45172 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ -# Molecule +
-[![CI][ci-image]][ci-url] [![Codecov][codecov-image]][codecov-url] [![NPM downloads][download-img]][download-url] + watchman-logo +

Molecule

+

A lightweight Web IDE UI Framework

+ +[![CI][ci-image]][ci-url] [![Codecov][codecov-image]][codecov-url] [![NPM downloads][download-img]][download-url] [![NPM version][npm-version]][npm-version-url] + +
[ci-image]: https://github.com/DTStack/molecule/actions/workflows/main.yml/badge.svg [ci-url]: https://github.com/DTStack/molecule/actions/workflows/main.yml @@ -8,14 +14,25 @@ [codecov-url]: https://codecov.io/gh/DTStack/molecule [download-img]: https://img.shields.io/npm/dm/@dtinsight/molecule.svg?style=flat [download-url]: https://www.npmjs.com/package/@dtinsight/molecule +[npm-version]: https://img.shields.io/npm/v/@dtinsight/molecule.svg?style=flat-square +[npm-version-url]: https://www.npmjs.com/package/@dtinsight/molecule + +[中文](./README-zhCN.md) | [English](./README.md) -A lightweight Web IDE UI Framework, built with React.js, and inspired by the Visual Studio Code. +The **Molecule** is a lightweight **Web IDE UI** Framework built with React.js,and inspired by the VSCode. We also provide the Extension APIs the seem like VSCode, to help developers extend the Workbench easily. The Molecule integrates with React.js applications is simple. It has applied to many [DTStack](https://www.dtstack.com/) inner projects. + +[Online Preview](https://github.com/DTStack/molecule-examples) ## Features -- Provides the default IDE Workbench same as the Visual Studio Code -- Easy to extend the default IDE Workbench via the Extension -- Atomic React Components, Easy to customize the IDE UI +- Builtin the VSCode **Workbench** UI +- Compatible with the VSCode **ColorTheme** +- Customize the Workbench via **React Component** easily +- Builtin Monaco-Editor **Command Palette, Keybinding** features +- Support the **i18n**, builtin zhCN, and English +- Builtin **Settings**, support to edit and extend via the Extension +- Builtin basic **Explorer, Search** components, and support extending via the Extension +- **Typescript** Ready ## Installation @@ -34,7 +51,7 @@ import { MoleculeProvider, Workbench } from '@dtinsight/molecule'; import '@dtinsight/molecule/esm/style/mo.css'; const App = () => ( - + ); @@ -42,14 +59,50 @@ const App = () => ( ReactDOM.render(, document.getElementById('root')); ``` +The `extension` is the Extension applications entry, more details about Extension, please read the [Quick Start](./website/docs/guides/the-first-extension.md). + ## Document -Refer to the [Docs](./docs). +- [Introduction](./website/docs/introduction.md) +- [Quick Start](./website/docs/.md) +- [API](./website/docs/api/index.md) +- [Extend Workbench](./website/docs/guides/extends-workbench.md) +- [More Docs](./website/docs). +- [Examples](https://github.com/DTStack/molecule-examples) + +## Development + +```bash +git clone git@github.com:DTStack/molecule.git +``` + +Clone the source code into your local + +**Development Mode** + +```bash +yarn # Install dependencies + +yarn dev # Start dev mode +``` + +The Molecule using the **Storybook** to manage and develop the React components, the default visiting address is `http://localhost:6006/`. + +**Build & Preview** + +```bash +yarn build # Compile to ESM +yarn web # Web Preview Mode +``` + +We compile the source code into the ES6 modules and output to the **`esm`** folder. Besides the Storybook development mode, there also builtin a **Web Preview** mode using the ESM modules. ## Contributing Refer to the [CONTRIBUTING](./CONTRIBUTING.md). -## Licence +## License + +Copyright © DTStack. All rights reserved. -MIT +Licensed under the MIT license. diff --git a/build/gulpfile.js b/build/gulpfile.js index 9944b7657..189e82b1d 100644 --- a/build/gulpfile.js +++ b/build/gulpfile.js @@ -5,6 +5,7 @@ const through = require('through2'); const rimraf = require('rimraf'); const sass = require('gulp-sass')(require('sass')); const aliasImporter = require('node-sass-alias-importer'); +const varables = require('./variables'); const tsProject = ts.createProject('../tsconfig.build.json'); const output = '../esm'; @@ -33,6 +34,21 @@ function relativeImport(compilerOptions) { }); } +function removeDevelopmentVariables() { + return through.obj(function (file, encoding, cb) { + if (!file.contents) return; + let content = file.contents.toString('utf-8'); + // 后续不行的话 用 babel 来基于 AST 来清除指定代码块 + const reg = new RegExp(Object.keys(varables).join('|')); + if (reg.test(content)) { + content = content.replace(reg, 'false'); + } + file.contents = Buffer.from(content); + this.push(file); + cb(); + }); +} + gulp.task('clean', function (cb) { rimraf(output, cb); }); @@ -42,6 +58,7 @@ gulp.task('build:esm', function () { .src() .pipe(tsProject()) .pipe(relativeImport(tsProject.config.compilerOptions)) + .pipe(removeDevelopmentVariables()) .on('error', function (error, callback) { console.error(error.stack); }) diff --git a/build/preinstall.js b/build/preinstall.js index 3620a0c57..0862bd1a0 100644 --- a/build/preinstall.js +++ b/build/preinstall.js @@ -2,14 +2,11 @@ let err = false; const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]); -if (majorNodeVersion < 10 || majorNodeVersion > 14) { - console.error('\033[1;31m*** Please use node >=10 and <=14.\033[0;0m'); +if (majorNodeVersion < 10) { + console.error('\033[1;31m*** Please use node >=10.\033[0;0m\n'); err = true; } -const cp = require('child_process'); - if (err) { - console.error(''); process.exit(1); } diff --git a/build/variables.js b/build/variables.js new file mode 100644 index 000000000..ebc153a0b --- /dev/null +++ b/build/variables.js @@ -0,0 +1,3 @@ +module.exports = { + __DEVELOPMENT__: true, +}; diff --git a/build/webpack.base.js b/build/webpack.base.js index 54665dd51..d9b964e26 100644 --- a/build/webpack.base.js +++ b/build/webpack.base.js @@ -1,6 +1,7 @@ const path = require('path'); const webpack = require('webpack'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); +const varables = require('./variables'); module.exports = { mode: 'development', @@ -40,8 +41,6 @@ module.exports = { new MonacoWebpackPlugin({ languages: ['html', 'typescript', 'javascript', 'json', 'css'], }), - new webpack.DefinePlugin({ - __DEVELOPMENT__: true, - }), + new webpack.DefinePlugin(varables), ], }; diff --git a/package-lock.json b/package-lock.json index 872321ba8..84ab99a73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "molecule", - "version": "0.9.0-alpha.6", + "version": "0.9.0-beta.1", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ac401c084..76d3b7b30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dtinsight/molecule", - "version": "0.9.0-alpha.6", + "version": "0.9.0-beta.1", "description": "A Web IDE UI Framework built with React.js, inspired by VSCode.", "module": "./esm/index.js", "typings": "./esm/index.d.ts", @@ -39,9 +39,10 @@ "@dtinsight/dt-utils": "^1.0.3", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", + "@vscode/codicons": "^0.0.26", "immutability-helper": "^3.1.1", "lodash": "^4.17.21", - "monaco-editor": "^0.23.0", + "monaco-editor": "^0.30.1", "rc-dialog": "8.2.1", "rc-textarea": "~0.3.1", "rc-tooltip": "^5.1.1", @@ -51,8 +52,7 @@ "react-scrollbars-custom": "^4.0.25", "react-split-pane": "^2.0.3", "reflect-metadata": "^0.1.13", - "tsyringe": "4.5.0", - "vscode-codicons": "^0.0.12" + "tsyringe": "4.5.0" }, "devDependencies": { "@babel/core": "^7.12.16", @@ -91,7 +91,7 @@ "husky": "^4.3.0", "jest": "^26.0.1", "jest-canvas-mock": "^2.3.1", - "monaco-editor-webpack-plugin": "^3.0.1", + "monaco-editor-webpack-plugin": "^6.0.0", "node-sass-alias-importer": "^1.0.2", "prettier": "^2.1.2", "pretty-quick": "^3.1.0", diff --git a/src/common/treeUtil.ts b/src/common/treeUtil.ts index 07c808e83..69f1f4a55 100644 --- a/src/common/treeUtil.ts +++ b/src/common/treeUtil.ts @@ -1,4 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; +import type { UniqueId } from 'mo/common/types'; import logger from './logger'; interface IWithIdProps { diff --git a/src/components/icon/index.tsx b/src/components/icon/index.tsx index f5a521717..e61516917 100644 --- a/src/components/icon/index.tsx +++ b/src/components/icon/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { classNames, prefixClaName } from 'mo/common/className'; -import 'vscode-codicons/dist/codicon.css'; +import '@vscode/codicons/dist/codicon.css'; import { ComponentProps } from 'react'; export interface IIconProps extends ComponentProps<'span'> { diff --git a/src/components/list/item.tsx b/src/components/list/item.tsx index afb8a9f38..8f9f815b7 100644 --- a/src/components/list/item.tsx +++ b/src/components/list/item.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { classNames, getBEMElement, getBEMModifier } from 'mo/common/className'; +import type { UniqueId } from 'mo/common/types'; import { defaultListClassName } from './list'; export interface IItemProps extends Omit, 'id'> { - id: string; + id: UniqueId; disabled?: boolean; - disable?: string; - active?: string; + disable?: UniqueId; + active?: UniqueId; onClick?(event: React.MouseEvent, item?: IItemProps): void; } @@ -47,7 +48,12 @@ export function Item(props: React.PropsWithChildren) { active === id ? itemActiveClassName : '' ); return ( -
  • +
  • {children}
  • ); diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx index 6c768fc00..e45384c2e 100644 --- a/src/components/list/list.tsx +++ b/src/components/list/list.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { prefixClaName, classNames, getBEMModifier } from 'mo/common/className'; import { ComponentProps, useEffect, useState } from 'react'; import { cloneReactChildren } from 'mo/react'; +import type { UniqueId } from 'mo/common/types'; import { IItemProps } from './item'; export interface IListProps extends Omit, 'onSelect'> { @@ -16,7 +17,7 @@ export interface IListProps extends Omit, 'onSelect'> { /** * It's used to disable specific item, the value of disable is id string */ - disable?: string; + disable?: UniqueId; /** * Listen to the select event of List * @param event React mouse event @@ -53,8 +54,8 @@ export function List(props: React.PropsWithChildren) { ...restProps } = props; - const [active, setActive] = useState(current); - const [isDisable, setIsDisable] = useState(disable); + const [active, setActive] = useState(current); + const [isDisable, setIsDisable] = useState(disable); useEffect(() => { if (active !== current) { diff --git a/src/components/menu/__tests__/__snapshots__/menu.test.tsx.snap b/src/components/menu/__tests__/__snapshots__/menu.test.tsx.snap index f75063df8..e79d39a13 100644 --- a/src/components/menu/__tests__/__snapshots__/menu.test.tsx.snap +++ b/src/components/menu/__tests__/__snapshots__/menu.test.tsx.snap @@ -15,6 +15,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = ` >
  • @@ -76,6 +77,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = `
  • @@ -137,6 +139,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = `
  • @@ -198,6 +201,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = `
  • @@ -257,6 +261,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = `
  • @@ -364,6 +369,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = `
  • @@ -409,6 +415,7 @@ exports[`Test the Menu Component Match the List snapshot 1`] = `
  • diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx index 2d794834d..89b793c7f 100644 --- a/src/components/menu/menu.tsx +++ b/src/components/menu/menu.tsx @@ -1,8 +1,11 @@ import React, { useEffect, useCallback, useRef } from 'react'; import { classNames } from 'mo/common/className'; -import { MenuItem } from './menuItem'; -import { isHorizontal, ISubMenuProps, MenuMode, SubMenu } from './subMenu'; import { debounce } from 'lodash'; +import { mergeFunctions } from 'mo/common/utils'; +import { cloneReactChildren } from 'mo/react'; +import { em2Px } from 'mo/common/css'; +import { getRelativePosition, triggerEvent } from 'mo/common/dom'; + import { activeClassName, defaultMenuClassName, @@ -10,11 +13,9 @@ import { horizontalMenuClassName, verticalMenuClassName, } from './base'; -import { mergeFunctions } from 'mo/common/utils'; -import { cloneReactChildren } from 'mo/react'; -import { em2Px } from 'mo/common/css'; -import { getRelativePosition, triggerEvent } from 'mo/common/dom'; import { Divider } from './divider'; +import { MenuItem } from './menuItem'; +import { isHorizontal, ISubMenuProps, MenuMode, SubMenu } from './subMenu'; export type IMenuProps = ISubMenuProps; @@ -64,13 +65,14 @@ export function Menu(props: React.PropsWithChildren) { onClick, trigger = 'hover', title, - ...custom + ...restProps } = props; const menuRef = useRef(null); const isMouseInMenu = useRef(false); let content = cloneReactChildren(children, { onClick }); // Only when the trigger is hover need to set the delay const delay = trigger === 'hover' ? 200 : 0; + const modeClassName = mode === MenuMode.Horizontal ? horizontalMenuClassName @@ -81,12 +83,13 @@ export function Menu(props: React.PropsWithChildren) { const renderMenusByData = (menus: IMenuProps[]) => { return menus.map((item: IMenuProps) => { if (item.type === 'divider') return ; + const handleClick = mergeFunctions(onClick, item.onClick); if (item.data && item.data.length > 0) { return ( @@ -140,7 +143,12 @@ export function Menu(props: React.PropsWithChildren) { } visibleMenuItem(liDom); const subMenu = liDom?.querySelector('ul') || undefined; - setPositionForSubMenu(liDom, subMenu, isHorizontal(mode)); + const subMenuMode = liDom?.dataset.mode || mode; + setPositionForSubMenu( + liDom, + subMenu, + isHorizontal(subMenuMode as MenuMode) + ); } }, delay); @@ -199,7 +207,7 @@ export function Menu(props: React.PropsWithChildren) { // prevent JSX render in HTMLElement {...(typeof title === 'string' ? { title } : {})} {...getEventListener()} - {...custom} + {...restProps} > {content} diff --git a/src/components/menu/menuItem.tsx b/src/components/menu/menuItem.tsx index 997dd3f21..9f78e42d0 100644 --- a/src/components/menu/menuItem.tsx +++ b/src/components/menu/menuItem.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { classNames } from 'mo/common/className'; -import { Icon } from '../icon'; +import type { HTMLElementProps, UniqueId } from 'mo/common/types'; + import { checkClassName, disabledClassName, @@ -9,8 +10,10 @@ import { labelClassName, menuContentClassName, } from './base'; +import { Icon } from '../icon'; -export interface IMenuItemProps extends Omit { +export interface IMenuItemProps extends HTMLElementProps { + id: UniqueId; /** * The name of icon */ @@ -20,7 +23,6 @@ export interface IMenuItemProps extends Omit { * Item Name */ name?: string; - title?: string; disabled?: boolean; /** * The description of keybinding @@ -37,7 +39,9 @@ export interface IMenuItemProps extends Omit { [key: string]: any; } -export function MenuItem(props: React.PropsWithChildren) { +export function MenuItem( + props: React.PropsWithChildren> +) { const { icon, disabled = false, @@ -48,7 +52,9 @@ export function MenuItem(props: React.PropsWithChildren) { children, name, title, - ...custom + id, + sortIndex, + ...restProps } = props; const events = { @@ -64,10 +70,12 @@ export function MenuItem(props: React.PropsWithChildren) { className, disabled ? disabledClassName : null )} + id={id?.toString()} + data-sort={sortIndex} // prevent render JSX title in HTMLElement {...(typeof title === 'string' ? { title } : {})} {...events} - {...custom} + {...restProps} >
    diff --git a/src/components/menu/style.scss b/src/components/menu/style.scss index 80b02ac83..fe692a3a9 100644 --- a/src/components/menu/style.scss +++ b/src/components/menu/style.scss @@ -80,6 +80,7 @@ .codicon { align-items: center; display: flex; + font-size: inherit; justify-content: center; } } diff --git a/src/components/menu/subMenu.tsx b/src/components/menu/subMenu.tsx index 390bbb0e2..c51ac597b 100644 --- a/src/components/menu/subMenu.tsx +++ b/src/components/menu/subMenu.tsx @@ -28,7 +28,7 @@ export function isVertical(mode: MenuMode) { return mode === MenuMode.Horizontal; } -export interface ISubMenuProps extends IMenuItemProps { +export interface ISubMenuProps extends Omit { /** * The event of show subMenu, default value is 'hover' */ @@ -50,7 +50,8 @@ export function SubMenu(props: React.PropsWithChildren) { children, onClick, title, - ...custom + sortIndex, + ...restProps } = props; const cNames = classNames(defaultSubMenuClassName, className); const isAlignHorizontal = isHorizontal(mode); @@ -63,7 +64,7 @@ export function SubMenu(props: React.PropsWithChildren) { style={{ opacity: '0', pointerEvents: 'none' }} data={data} onClick={onClick} - {...custom} + {...restProps} /> ) : ( ) { disabled ? disabledClassName : null )} data-submenu + data-mode={mode} + data-sort={sortIndex} // prevent render JSX title in HTMLElement {...(typeof title === 'string' ? { title } : {})} - {...custom} + {...restProps} >
    {typeof icon === 'string' ? ( diff --git a/src/components/tabs/__tests__/__snapshots__/tabs.test.tsx.snap b/src/components/tabs/__tests__/__snapshots__/tabs.test.tsx.snap index 459e268c7..109dd2d02 100644 --- a/src/components/tabs/__tests__/__snapshots__/tabs.test.tsx.snap +++ b/src/components/tabs/__tests__/__snapshots__/tabs.test.tsx.snap @@ -11,8 +11,6 @@ exports[`The Tabs Components The snapshot 1`] = ` className="mo-tab__item" onClick={[Function]} onContextMenu={[Function]} - onMouseOut={[Function]} - onMouseOver={[Function]} > test-title-1 - +
    + +
    test-title-2 - +
    + +
    diff --git a/src/components/tabs/__tests__/tab.test.tsx b/src/components/tabs/__tests__/tab.test.tsx index db58090f4..5544fbdad 100644 --- a/src/components/tabs/__tests__/tab.test.tsx +++ b/src/components/tabs/__tests__/tab.test.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import { cleanup, fireEvent, render, waitFor } from '@testing-library/react'; +import { cleanup, fireEvent, render } from '@testing-library/react'; import '@testing-library/jest-dom'; import { - ITabEvent, - ITabProps, + ITabComponent, Tab, tabItemActiveClassName, tabItemClassName, @@ -13,32 +12,18 @@ import { dragToTargetNode } from '@test/utils'; const tabData = { id: '1', - active: false, index: 1, name: 'test', }; -function DTab(args: ITabProps & ITabEvent) { +function DTab(args: Partial) { return ( - + ); } -function mockClientOffset(boundingRectSize: number, size: number) { - // @ts-ignore - HTMLDivElement.prototype.getBoundingClientRect = jest.fn(() => ({ - right: boundingRectSize, - left: boundingRectSize, - })); - - // @ts-ignore - Event.prototype.clientX = size; - // @ts-ignore - Event.prototype.clientY = size; -} - afterEach(cleanup); describe('The Tab Component', () => { @@ -50,12 +35,12 @@ describe('The Tab Component', () => { test('Should render icon in tab', () => { const { container, getByTestId, rerender } = render( - + ); expect(container.querySelector('.codicon-placeholder')).not.toBeNull(); - rerender(} />); + rerender( }} />); expect(getByTestId('icon')).toBeInTheDocument(); }); @@ -65,30 +50,19 @@ describe('The Tab Component', () => { expect(wrapper.classList).toContain(tabItemActiveClassName); }); - test('Should render extra when editable and closable', async () => { + test('Should render close icon', async () => { const mockFn = jest.fn(); - const { container } = render( - - ); + const { container } = render(); const wrapper = container.querySelector(`.${tabItemClassName}`)!; - expect(wrapper.childElementCount).toBe(2); + // text element doesn't count + expect(wrapper.childElementCount).toBe(1); fireEvent.mouseOver(wrapper); fireEvent.click(wrapper.children[0].firstChild!); - fireEvent.click(wrapper.children[1].firstChild!); - await waitFor(() => { - expect(mockFn).toBeCalledTimes(2); - expect(mockFn.mock.calls[0][0]).toBe(tabData.id); - }); - - fireEvent.mouseOut(wrapper); - fireEvent.click(wrapper.children[0].firstChild!); - mockFn.mockClear(); - await waitFor(() => { - expect(mockFn).not.toBeCalled(); - }); + expect(mockFn).toBeCalledTimes(1); + expect(mockFn.mock.calls[0][0]).toBe(tabData.id); }); test('Should trigger contextmenu event', () => { @@ -105,9 +79,9 @@ describe('The Tab Component', () => { const mockFn = jest.fn(); const { container } = render( - - - + + + ); @@ -115,82 +89,51 @@ describe('The Tab Component', () => { `.${tabItemClassName}` )!; - // Same source and target will do nothing; + // Tab doesn't distinguish between source and target dragToTargetNode(tabs[1], tabs[1]); - expect(mockFn).not.toBeCalled(); - - // normal - dragToTargetNode(tabs[1], tabs[0]); expect(mockFn).toBeCalled(); - expect(mockFn.mock.calls[0][0]).toBe(2); - expect(mockFn.mock.calls[0][1]).toBe(1); - }); - test('Should do nothing when drag up', () => { - const originalBoundingClientRect = - HTMLDivElement.prototype.getBoundingClientRect; - //@ts-ignore - const originalClientX = Event.prototype.clientX; - //@ts-ignore - const originalClientY = Event.prototype.clientY; + expect(mockFn.mock.calls[0][0]).toEqual({ id: '2', name: 'test2' }); + expect(mockFn.mock.calls[0][1]).toEqual({ id: '2', name: 'test2' }); + }); - const mockFn = jest.fn(); - const { container } = render( - - - - - + test('Support to render status icon', () => { + const { container, rerender, getByTestId } = render( + ); - const tabs = container.querySelectorAll( - `.${tabItemClassName}` - )!; + expect( + container.querySelector('.codicon-primitive-dot') + ).not.toBeNull(); - mockClientOffset(5, 10); - mockFn.mockClear(); - dragToTargetNode(tabs[1], tabs[0]); - expect(mockFn).not.toBeCalled(); - - // reset mock function - HTMLDivElement.prototype.getBoundingClientRect = originalBoundingClientRect; - // @ts-ignore - Event.prototype.clientX = originalClientX; // @ts-ignore - Event.prototype.clientY = originalClientY; + rerender(); + + // render a close icon in default + expect(container.querySelector('.codicon-close')).not.toBeNull(); + + rerender( + test, + }} + /> + ); + + expect(getByTestId('test-icon')).toBeInTheDocument(); }); - test('Should do nothing when drag down', () => { - const originalBoundingClientRect = - HTMLDivElement.prototype.getBoundingClientRect; - //@ts-ignore - const originalClientX = Event.prototype.clientX; - //@ts-ignore - const originalClientY = Event.prototype.clientY; + test('Should NOT render close icon when closable is false', () => { + const { container, rerender } = render(); - const mockFn = jest.fn(); - const { container } = render( - - - - - - ); + // There is a close icon when didn't specify the closable attr + expect(container.querySelector('.codicon-close')).not.toBeNull(); - const tabs = container.querySelectorAll( - `.${tabItemClassName}` - )!; + rerender(); + expect(container.querySelector('.codicon-close')).toBeNull(); - mockClientOffset(20, 10); - mockFn.mockClear(); - dragToTargetNode(tabs[0], tabs[1]); - expect(mockFn).not.toBeCalled(); - - // reset mock function - HTMLDivElement.prototype.getBoundingClientRect = originalBoundingClientRect; - // @ts-ignore - Event.prototype.clientX = originalClientX; - // @ts-ignore - Event.prototype.clientY = originalClientY; + rerender(); + expect(container.querySelector('.codicon-close')).not.toBeNull(); }); }); diff --git a/src/components/tabs/__tests__/tabExtra.test.tsx b/src/components/tabs/__tests__/tabExtra.test.tsx index 0279c80ed..a0cf9e1e1 100644 --- a/src/components/tabs/__tests__/tabExtra.test.tsx +++ b/src/components/tabs/__tests__/tabExtra.test.tsx @@ -1,98 +1,53 @@ import React from 'react'; -import { cleanup, fireEvent, render, waitFor } from '@testing-library/react'; +import { cleanup, fireEvent, render } from '@testing-library/react'; import '@testing-library/jest-dom'; import TabExtra from '../tabExtra'; - -const mockData = { - modified: false, - active: false, - buttonHover: false, -}; +import { expectFnCalled } from '@test/utils'; afterEach(cleanup); describe('The Tab Component', () => { - test('Should render default placeholder', () => { - const { container } = render( - + test('Should render default value', async () => { + const { findByTestId } = render( +
    1
    } /> ); - const wrapper = container.querySelector('.test'); - const context = wrapper?.querySelector('.test__placeholder'); - expect(context).toBeInTheDocument(); + expect(await findByTestId('test')).toBeInTheDocument(); }); - test('Should render dot icon modified', () => { - const { container } = render( - - ); + test('Should render with classNames', () => { + const { container } = render(); const wrapper = container.querySelector('.test'); - const context = wrapper?.querySelector('.test__dot'); - expect(context).toBeInTheDocument(); + const innerWrapper = wrapper?.querySelector('.test__button'); + expect(innerWrapper).toBeInTheDocument(); }); - test('Should render close icon when buttonHover is true', () => { - const { container } = render( - - ); - const closeIcon = container?.querySelector('.codicon-close'); - expect(closeIcon).toBeInTheDocument(); - }); + test('Should support to the click event', () => { + expectFnCalled((fn) => { + const { container } = render( + + ); + const wrapper = container.querySelector('.test'); + const innerWrapper = wrapper?.querySelector('.test__button'); + expect(innerWrapper).not.toBeUndefined(); - test('Should render close icon when active is true', () => { - const { container } = render( - - ); - const closeIcon = container?.querySelector('.codicon-close'); - expect(closeIcon).toBeInTheDocument(); - }); - - test('Should render close icon when mouseover', async () => { - const { container } = render( - - ); - const wrapper = container.querySelector('.test'); - fireEvent.mouseOver(wrapper!); - - await waitFor(() => { - const closeIcon = wrapper?.querySelector('.codicon-close'); - expect(closeIcon).toBeInTheDocument(); - }); - - fireEvent.mouseOut(wrapper!); - - await waitFor(() => { - const closeIcon = wrapper?.querySelector('.codicon-close'); - expect(closeIcon).not.toBeInTheDocument(); + fireEvent.click(innerWrapper!); }); }); - test('Should hide after click event', async () => { + test('Should pass through hover status when mouseOver and mouseOut', async () => { const mockFn = jest.fn(); const { container } = render( - + ); const wrapper = container.querySelector('.test'); - fireEvent.mouseOver(wrapper!); + expect(mockFn.mock.calls[0][0]).toBe(false); - await waitFor(() => { - const closeIcon = wrapper?.querySelector('.codicon-close'); - expect(closeIcon).toBeInTheDocument(); - }); - - fireEvent.click(container.querySelector('.test__button')!); + fireEvent.mouseOver(wrapper!); - await waitFor(() => { - const closeIcon = wrapper?.querySelector('.codicon-close'); - expect(closeIcon).not.toBeInTheDocument(); + expect(mockFn.mock.calls[1][0]).toBe(true); - expect(mockFn).toBeCalled(); - }); - }); - - test('Should have default value', () => { - const { container } = render(); - const context = container.querySelector('.__placeholder'); + fireEvent.mouseOut(wrapper!); - expect(context).toBeInTheDocument(); + expect(mockFn.mock.calls[2][0]).toBe(false); }); }); diff --git a/src/components/tabs/__tests__/tabs.test.tsx b/src/components/tabs/__tests__/tabs.test.tsx index 02168a6c7..685b78420 100644 --- a/src/components/tabs/__tests__/tabs.test.tsx +++ b/src/components/tabs/__tests__/tabs.test.tsx @@ -3,7 +3,7 @@ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react'; import renderer from 'react-test-renderer'; import '@testing-library/jest-dom'; import { Tabs, tabsClassName } from '..'; -import { ITabProps, tabItemActiveClassName } from '../tab'; +import { ITabProps, tabItemActiveClassName, tabItemClassName } from '../tab'; import { dragToTargetNode } from '@test/utils'; const mockData: ITabProps[] = [ @@ -23,6 +23,19 @@ const mockData: ITabProps[] = [ afterEach(cleanup); +function mockClientOffset(boundingRectSize: number, size: number) { + // @ts-ignore + HTMLDivElement.prototype.getBoundingClientRect = jest.fn(() => ({ + right: boundingRectSize, + left: boundingRectSize, + })); + + // @ts-ignore + Event.prototype.clientX = size; + // @ts-ignore + Event.prototype.clientY = size; +} + describe('The Tabs Components', () => { test('The snapshot', () => { const component = renderer.create(); @@ -110,4 +123,44 @@ describe('The Tabs Components', () => { }, ]); }); + + test('Should NOT onMove', () => { + const originalBoundingClientRect = + HTMLDivElement.prototype.getBoundingClientRect; + //@ts-ignore + const originalClientX = Event.prototype.clientX; + //@ts-ignore + const originalClientY = Event.prototype.clientY; + + const mockFn = jest.fn(); + const { container } = render( + + ); + + const tabs = container.querySelectorAll( + `.${tabItemClassName}` + )!; + + // drag up + mockClientOffset(5, 10); + dragToTargetNode(tabs[1], tabs[0]); + expect(mockFn).not.toBeCalled(); + + // drag down + mockClientOffset(20, 10); + dragToTargetNode(tabs[0], tabs[1]); + expect(mockFn).not.toBeCalled(); + + // drag to itself + mockClientOffset(20, 10); + dragToTargetNode(tabs[0], tabs[0]); + expect(mockFn).not.toBeCalled(); + + // reset mock function + HTMLDivElement.prototype.getBoundingClientRect = originalBoundingClientRect; + // @ts-ignore + Event.prototype.clientX = originalClientX; + // @ts-ignore + Event.prototype.clientY = originalClientY; + }); }); diff --git a/src/components/tabs/index.tsx b/src/components/tabs/index.tsx index 257932149..0e392d7aa 100644 --- a/src/components/tabs/index.tsx +++ b/src/components/tabs/index.tsx @@ -11,33 +11,51 @@ import { import { Tab, ITabProps } from './tab'; import DragAndDrop from './dragAndDrop'; +import type { UniqueId } from 'mo/common/types'; export type TabsType = 'line' | 'card'; -export interface ITabsProps extends React.ComponentProps { +/** + * TODO: Get rid of the generic and remove the ComponentProps + */ +export interface ITabsProps extends React.ComponentProps { className?: string; + style?: React.CSSProperties; + role?: string; + /** + * @deprecated For now, we don't need to control the global closable + */ closable?: boolean; + /** + * @deprecated For now, we don't need to control the global editable + */ editable?: boolean; - data?: ITabProps[]; - activeTab?: string; + data?: ITabProps[]; + activeTab?: UniqueId; + /** + * Default is line + */ type?: TabsType; - style?: React.CSSProperties; - onCloseTab?: (key?: string) => void; - onMoveTab?: (tabs: ITabProps[]) => void; - onSelectTab?: (key?: string) => void; + onCloseTab?: (key: UniqueId) => void; + onContextMenu?: (e: React.MouseEvent, tab: ITabProps) => void; + onMoveTab?: (tabs: ITabProps[]) => void; + onSelectTab?: (key: UniqueId) => void; } export const tabsClassName = prefixClaName('tabs'); export const tabsHeader = getBEMElement(tabsClassName, 'header'); -export function Tabs(props: ITabsProps) { +export function Tabs(props: ITabsProps) { const { + role, activeTab, className, data = [], type = 'line', style, onMoveTab, - ...resetProps + onCloseTab, + onSelectTab, + onContextMenu, } = props; const onChangeTab = useCallback( (dragIndex, hoverIndex) => { @@ -54,6 +72,29 @@ export function Tabs(props: ITabsProps) { [data] ); + const handleDrag = ( + source: ITabProps, + target: ITabProps, + infos: Record + ) => { + const dragIndex = data.indexOf(source); + const hoverIndex = data.indexOf(target); + const { hoverClientX, hoverMiddleX } = infos; + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + // drag down + if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { + return; + } + // drag up + if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) { + return; + } + onChangeTab?.(dragIndex, hoverIndex); + }; + return (
    (props: ITabsProps) { getBEMModifier(tabsClassName, type as string), className )} + role={role} >
    - {data?.map((tab: ITabProps, index: number) => { + {data.map((tab, index) => { return ( ); })} diff --git a/src/components/tabs/style.scss b/src/components/tabs/style.scss index 1aeff4598..e780d1047 100644 --- a/src/components/tabs/style.scss +++ b/src/components/tabs/style.scss @@ -58,31 +58,24 @@ } } + &:hover { + #{$tab}__item__op { + opacity: 1; + } + } + + &__status, + &__op { + font-size: 0; + } + &__op { color: var(--icon-foreground); margin-left: 10px; + opacity: 0; transform: translateY(1px); width: 20px; - &__dot { - display: block; - height: 18px; - position: relative; - width: 18px; - - &::after { - background: var(--tab-activeForeground); - border-radius: 50%; - content: ''; - display: block; - height: 9px; - left: 5px; - position: relative; - top: 5px; - width: 9px; - } - } - &__close { cursor: pointer; display: block; @@ -90,22 +83,16 @@ height: 18px; width: 18px; } - - &__placeholder { - display: block; - height: 18px; - width: 18px; - } - - &__button { - font-size: 0; - } } &--active { background: var(--tab-activeBackground); border-bottom-color: var(--tab-activeBorder); color: var(--tab-activeForeground); + + #{$tab}__item__op { + opacity: 1; + } } &--close { diff --git a/src/components/tabs/tab.tsx b/src/components/tabs/tab.tsx index b6750f41b..db1e7effc 100644 --- a/src/components/tabs/tab.tsx +++ b/src/components/tabs/tab.tsx @@ -1,12 +1,7 @@ import React from 'react'; -import { useCallback, useRef, useState } from 'react'; +import { useRef } from 'react'; import { findDOMNode } from 'react-dom'; -import { - DragSourceMonitor, - DropTargetMonitor, - useDrag, - useDrop, -} from 'react-dnd'; +import { useDrag, useDrop } from 'react-dnd'; import { classNames, @@ -16,23 +11,43 @@ import { } from 'mo/common/className'; import TabExtra from './tabExtra'; import { Icon } from '../icon'; +import type { UniqueId } from 'mo/common/types'; export interface ITabEvent { - onMoveTab?: (dragIndex: number, hoverIndex: number) => void; - onCloseTab?: (key?: string) => void; - onSelectTab?: (key?: string) => void; + onDrag?: ( + source: ITabProps, + target: ITabProps, + dragInfos: Record + ) => void; + onCloseTab?: (key: UniqueId) => void; + onSelectTab?: (key: UniqueId) => void; onContextMenu?: ( event: React.MouseEvent, tab: ITabProps ) => void; } -export interface ITabProps extends ITabEvent { + +type ITabStatus = 'edited'; +/** + * The type definition for the Tab data construct + */ +export interface ITabProps { + /** + * @deprecated Tab doesn't need this property, but the type extends from tab need + */ active?: boolean; + /** + * Mark the tab status to be closable, + * Default is true + */ closable?: boolean; + /** + * Mark the tab status to be editing + */ editable?: boolean; + status?: ITabStatus | ((tab: ITabProps) => JSX.Element); icon?: string | JSX.Element; - index?: number; - id?: string; + id: UniqueId; name?: string; renderPane?: ((item: P) => React.ReactNode) | React.ReactNode; data?: T; @@ -46,56 +61,34 @@ export const tabItemActiveClassName = getBEMModifier( ); export const tabItemLabelClassName = getBEMElement(tabItemClassName, 'label'); -export function Tab(props: ITabProps) { - const { - active, - name, - closable, - editable, - data, - id, - index, - icon, - onCloseTab, - onMoveTab, - onSelectTab, - onContextMenu, - ...resetProps - } = props; +/** + * The type definition for The Tab Component + */ +export type ITabComponent = { tab: ITabProps; active?: boolean } & ITabEvent; + +export function Tab({ tab, active, ...restEvents }: ITabComponent) { + const { name, closable, id, icon, status } = tab; + const { onCloseTab, onSelectTab, onContextMenu, onDrag } = restEvents; + const ref = useRef(null); - const [hover, setHover] = useState(false); - const handleMouseOver = () => setHover(true); - const handleMouseOut = () => setHover(false); - const handleOnContextMenu = useCallback( - (event: React.MouseEvent) => { - event.preventDefault(); - onContextMenu?.(event, props); - }, - [props] - ); + const handleOnContextMenu = (event: React.MouseEvent) => { + event.preventDefault(); + onContextMenu?.(event, tab); + }; const [, drag] = useDrag({ - collect: (monitor: DragSourceMonitor) => ({ + collect: (monitor) => ({ isDragging: monitor.isDragging(), }), - item: { type: 'DND_NODE', id, index }, + item: { type: 'DND_NODE', tab }, }); const [, drop] = useDrop({ accept: 'DND_NODE', - hover( - item: { type: string; index: number }, - monitor: DropTargetMonitor - ) { + hover(item: { type: string; tab: ITabProps }, monitor) { if (!ref.current) return; const component = ref.current; - const dragIndex = monitor.getItem().index; - const hoverIndex = index!; - // Don't replace items with themselves - if (dragIndex === hoverIndex) { - return; - } /** * TODO: bad code needs to be removed */ @@ -108,16 +101,12 @@ export function Tab(props: ITabProps) { const hoverClientX = (clientOffset as { x: number; y: number }).x - hoverBoundingRect.left; - // drag down - if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) { - return; - } - // drag up - if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) { - return; - } - onMoveTab?.(dragIndex, hoverIndex); - monitor.getItem().index = hoverIndex; + + const dragInfo = { + hoverMiddleX, + hoverClientX, + }; + onDrag?.(item.tab, tab, dragInfo); }, }); @@ -127,6 +116,25 @@ export function Tab(props: ITabProps) { return typeof icon === 'string' ? : icon; }; + const renderStatus = ( + status?: ITabStatus | ((tab: ITabProps) => JSX.Element), + isHover?: boolean + ) => { + if (status && !isHover) { + if (typeof status === 'function') { + return status(tab); + } + switch (status) { + case 'edited': + return ; + + default: + return ; + } + } + return ; + }; + return (
    (props: ITabProps) { [tabItemActiveClassName]: active, })} onClick={(event: React.MouseEvent) => onSelectTab?.(id)} - onMouseOver={handleMouseOver} - onMouseOut={handleMouseOut} onContextMenu={handleOnContextMenu} > {icon && ( @@ -144,23 +150,14 @@ export function Tab(props: ITabProps) { )} {name} - {editable && ( - onCloseTab?.(id)} - modified={data?.modified || false} - {...resetProps} - /> - )} - {closable && ( + {(typeof closable === 'undefined' || closable) && ( onCloseTab?.(id)} - {...resetProps} + classNames={getBEMElement( + tabItemClassName, + status ? 'status' : 'op' + )} + onClick={() => onCloseTab?.(id)} + renderStatus={(isHover) => renderStatus(status, isHover)} /> )}
    diff --git a/src/components/tabs/tabExtra.tsx b/src/components/tabs/tabExtra.tsx index 6324db396..a9f7ac546 100644 --- a/src/components/tabs/tabExtra.tsx +++ b/src/components/tabs/tabExtra.tsx @@ -1,23 +1,13 @@ -import React from 'react'; -import { useState } from 'react'; - +import React, { useState } from 'react'; import { getBEMElement } from 'mo/common/className'; -import { Icon } from 'mo/components/icon'; -export interface ITabExtraProps { - modified?: boolean; - active?: boolean; - buttonHover?: boolean; - onClick?: (event: React.MouseEvent) => void; + +interface ITabExtraProps { classNames?: string; + onClick?: () => void; + renderStatus?: (hover: boolean) => JSX.Element; } -export default function TabExtra({ - classNames = '', - modified = false, - onClick, - active = false, - buttonHover = false, -}: ITabExtraProps) { +export default ({ onClick, classNames = '', renderStatus }: ITabExtraProps) => { const [hover, setHover] = useState(false); const handleMouseOver = () => { @@ -28,40 +18,18 @@ export default function TabExtra({ setHover(false); }; - const handleClick = (e: React.MouseEvent) => { - e.stopPropagation(); - onClick?.(e); - handleMouseOut(); - }; - - const renderTabExtra = () => { - if ( - hover || - (!active && buttonHover && !modified) || - (active && !modified) - ) { - return ( -
    - -
    - ); - } - if (modified) { - return ; - } - return ; - }; - return ( - {renderTabExtra()} +
    + {renderStatus?.(hover)} +
    ); -} +}; diff --git a/src/components/tree/index.tsx b/src/components/tree/index.tsx index 07a2bfd32..6e52a2306 100644 --- a/src/components/tree/index.tsx +++ b/src/components/tree/index.tsx @@ -11,6 +11,7 @@ import { unexpandTreeNodeClassName, } from './base'; import { TreeViewUtil } from 'mo/common/treeUtil'; +import type { UniqueId } from 'mo/common/types'; export interface ITreeNodeItemProps { /** diff --git a/src/controller/__tests__/notification.test.ts b/src/controller/__tests__/notification.test.ts index 7fc670a3c..2e2ed0cc1 100644 --- a/src/controller/__tests__/notification.test.ts +++ b/src/controller/__tests__/notification.test.ts @@ -13,12 +13,6 @@ const notificationService = container.resolve(NotificationService); const statusBarService = container.resolve(StatusBarService); const builtinService = container.resolve(BuiltinService); -jest.mock('react-dom', () => { - return { - render: jest.fn(), - }; -}); - describe('The notification controller', () => { test('Should support initialize the service', () => { notificationController.initView(); @@ -94,34 +88,23 @@ describe('The notification controller', () => { expect(notificationService.getState().data).toHaveLength(0); }); - test('Should support to toggleNotifications', () => { - expect( - (notificationController as any)._notificationPane - ).toBeUndefined(); - expect(notificationService.getState().showNotifications).toBeFalsy(); - notificationController.toggleNotifications(); - - expect( - (notificationController as any)._notificationPane - ).not.toBeUndefined(); - expect(notificationService.getState().showNotifications).toBeTruthy(); - }); - test('Should support to execute onClick', () => { - expect(notificationService.getState().showNotifications).toBeTruthy(); + expect(notificationService.getState().showNotifications).toBeFalsy(); notificationController.onClick({} as any, { id: 'test' }); - expect(notificationService.getState().showNotifications).toBeFalsy(); + expect(notificationService.getState().showNotifications).toBeTruthy(); }); test('Should support to execute onActionBarClick', () => { - expect(notificationService.getState().showNotifications).toBeFalsy(); + notificationService.add([{ id: 1, value: 'test' }]); + expect(notificationService.getState().data).toHaveLength(1); notificationController.onActionBarClick({} as any, { id: constants.NOTIFICATION_CLEAR_ALL_ID, }); - expect(notificationService.getState().showNotifications).toBeTruthy(); + expect(notificationService.getState().data).toHaveLength(0); + expect(notificationService.getState().showNotifications).toBeTruthy(); notificationController.onActionBarClick({} as any, { id: constants.NOTIFICATION_HIDE_ID, }); diff --git a/src/controller/activityBar.ts b/src/controller/activityBar.ts index 09d7366bf..71b2df10f 100644 --- a/src/controller/activityBar.ts +++ b/src/controller/activityBar.ts @@ -16,16 +16,17 @@ import { } from 'mo/services'; import { CommandQuickAccessViewAction } from 'mo/monaco/quickAccessViewAction'; import { IMonacoService, MonacoService } from 'mo/monaco/monacoService'; +import type { UniqueId } from 'mo/common/types'; export interface IActivityBarController extends Partial { /** * Called when activity bar item is clicked */ - onClick?: (selectedKey: string, selectedNode: IActivityBarItem) => void; + onClick?: (selectedKey: UniqueId, selectedNode: IActivityBarItem) => void; /** * Called when activity bar item which is not global is changed */ - onChange?: (prevSelected?: string, nextSelected?: string) => void; + onChange?: (prevSelected?: UniqueId, nextSelected?: UniqueId) => void; onContextMenuClick?: ( e: React.MouseEvent, item: IMenuItemProps | undefined @@ -65,15 +66,15 @@ export class ActivityBarController } public readonly onClick = ( - selectedKey: string, + selectedKey: UniqueId, selctedNode: IActivityBarItem ) => { this.emit(ActivityBarEvent.OnClick, selectedKey, selctedNode); }; public readonly onChange = ( - prevSelected?: string, - nextSelected?: string + prevSelected?: UniqueId, + nextSelected?: UniqueId ) => { this.emit(ActivityBarEvent.OnChange, prevSelected, nextSelected); }; diff --git a/src/controller/editor.tsx b/src/controller/editor.tsx index 7b79321d1..3bd1bf5aa 100644 --- a/src/controller/editor.tsx +++ b/src/controller/editor.tsx @@ -20,30 +20,31 @@ import { IStatusBarService, StatusBarService, } from 'mo/services'; +import type { UniqueId } from 'mo/common/types'; export interface IEditorController extends Partial { groupSplitPos?: string[]; - open?(tab: IEditorTab, groupId?: number): void; + open?(tab: IEditorTab, groupId?: UniqueId): void; onClickContextMenu?: ( e: React.MouseEvent, item: IMenuItemProps, tabItem?: IEditorTab ) => void; - onCloseAll?: (group: number) => void; - onCloseTab?: (tabId: string, group: number) => void; - onCloseToLeft?: (tab: IEditorTab, group: number) => void; - onCloseToRight?: (tab: IEditorTab, group: number) => void; - onCloseOther?: (tab: IEditorTab, group: number) => void; - onCloseSaved?: (group: number) => void; + onCloseAll?: (group: UniqueId) => void; + onCloseTab?: (tabId: UniqueId, group: UniqueId) => void; + onCloseToLeft?: (tab: IEditorTab, group: UniqueId) => void; + onCloseToRight?: (tab: IEditorTab, group: UniqueId) => void; + onCloseOther?: (tab: IEditorTab, group: UniqueId) => void; + onCloseSaved?: (group: UniqueId) => void; onChangeEditorProps?: ( preProps: IMonacoEditorProps, nextProps: IMonacoEditorProps ) => void; - onMoveTab?: (updateTabs: IEditorTab[], group: number) => void; - onSelectTab?: (tabId: string, group: number) => void; + onMoveTab?: (updateTabs: IEditorTab[], group: UniqueId) => void; + onSelectTab?: (tabId: UniqueId, group: UniqueId) => void; onClickActions: (action: IEditorActionsProps) => void; - onUpdateEditorIns?: (editorInstance: any, groupId: number) => void; - onPaneSizeChange?: (newSize: number) => void; + onUpdateEditorIns?: (editorInstance: any, groupId: UniqueId) => void; + onPaneSizeChange?: (newSize: string[]) => void; } @singleton() export class EditorController extends Controller implements IEditorController { @@ -68,18 +69,24 @@ export class EditorController extends Controller implements IEditorController { BuiltInEditorOptions, } = this.builtinService.getModules(); - const builtinActions = builtInEditorInitialActions || []; - this.editorService.setDefaultActions(builtinActions); + const defaultActions = this.editorService.getDefaultActions(); + if (!defaultActions.length) { + const builtinActions = builtInEditorInitialActions || []; + this.editorService.setDefaultActions(builtinActions); + } - const builtinMenus = builtInEditorInitialMenu || []; - this.editorService.setDefaultMenus(builtinMenus); + const defaultMenus = this.editorService.getDefaultMenus(); + if (!defaultMenus.length) { + const builtinMenus = builtInEditorInitialMenu || []; + this.editorService.setDefaultMenus(builtinMenus); + } this.editorService.setState({ editorOptions: BuiltInEditorOptions || {}, }); } - public open(tab: IEditorTab, groupId?: number) { + public open(tab: IEditorTab, groupId?: UniqueId) { this.editorService.open(tab, groupId); } @@ -128,41 +135,41 @@ export class EditorController extends Controller implements IEditorController { } }; - public onCloseAll = (groupId: number) => { + public onCloseAll = (groupId: UniqueId) => { this.editorService.closeAll(groupId); this.emit(EditorEvent.OnCloseAll, groupId); }; - public onCloseTab = (tabId?: string, groupId?: number) => { + public onCloseTab = (tabId?: UniqueId, groupId?: UniqueId) => { if (tabId && groupId) { this.editorService.closeTab(tabId, groupId); this.emit(EditorEvent.OnCloseTab, tabId, groupId); } }; - public onCloseToRight = (tabItem: IEditorTab, groupId: number) => { + public onCloseToRight = (tabItem: IEditorTab, groupId: UniqueId) => { this.editorService.closeToRight(tabItem, groupId); this.emit(EditorEvent.OnCloseToRight, tabItem, groupId); }; - public onCloseToLeft = (tabItem: IEditorTab, groupId: number) => { + public onCloseToLeft = (tabItem: IEditorTab, groupId: UniqueId) => { this.editorService.closeToLeft(tabItem, groupId); this.emit(EditorEvent.OnCloseToLeft, tabItem, groupId); }; - public onCloseOther = (tabItem: IEditorTab, groupId: number) => { + public onCloseOther = (tabItem: IEditorTab, groupId: UniqueId) => { this.editorService.closeOther(tabItem, groupId); this.emit(EditorEvent.OnCloseOther, tabItem, groupId); }; - public onMoveTab = (updateTabs: IEditorTab[], groupId: number) => { + public onMoveTab = (updateTabs: IEditorTab[], groupId: UniqueId) => { this.editorService.updateGroup(groupId, { data: updateTabs, }); this.emit(EditorEvent.OnMoveTab, updateTabs, groupId); }; - public onSelectTab = (tabId: string, groupId: number) => { + public onSelectTab = (tabId: UniqueId, groupId: UniqueId) => { this.editorService.setActive(groupId, tabId); this.emit(EditorEvent.OnSelectTab, tabId, groupId); }; @@ -172,7 +179,7 @@ export class EditorController extends Controller implements IEditorController { */ public onUpdateEditorIns = ( editorInstance: MonacoEditor.IStandaloneCodeEditor, - groupId: number + groupId: UniqueId ) => { if (!editorInstance) return; @@ -187,7 +194,7 @@ export class EditorController extends Controller implements IEditorController { this.openTab( editorInstance, - tab?.id!, + tab!.id!.toString(), tab?.data?.value!, tab?.data?.language! ); @@ -223,13 +230,13 @@ export class EditorController extends Controller implements IEditorController { } }; - public onPaneSizeChange = (newSize) => { + public onPaneSizeChange = (newSize: string[]) => { this.groupSplitPos = newSize; }; private initEditorEvents( editorInstance: MonacoEditor.IStandaloneCodeEditor, - groupId: number + groupId: UniqueId ) { if (!editorInstance) return; diff --git a/src/controller/explorer/editorTree.tsx b/src/controller/explorer/editorTree.tsx index c433d2ec7..7939adaa0 100644 --- a/src/controller/explorer/editorTree.tsx +++ b/src/controller/explorer/editorTree.tsx @@ -15,15 +15,16 @@ import { } from 'mo/workbench/sidebar/explore/editorTree'; import { connect } from 'mo/react'; import { IActionBarItemProps, IMenuItemProps, ITabProps } from 'mo/components'; +import type { UniqueId } from 'mo/common/types'; export interface IEditorTreeController extends Partial { - readonly onClose?: (tabId: string, groupId: number) => void; - readonly onSelect?: (tabId: string, groupId: number) => void; - readonly onCloseGroup?: (groupId: number) => void; - readonly onSaveGroup?: (groupId: number) => void; + readonly onClose?: (tabId: UniqueId, groupId: UniqueId) => void; + readonly onSelect?: (tabId: UniqueId, groupId: UniqueId) => void; + readonly onCloseGroup?: (groupId: UniqueId) => void; + readonly onSaveGroup?: (groupId: UniqueId) => void; readonly onToolbarClick?: ( toolbar: IActionBarItemProps, - groupId: number + groupId: UniqueId ) => void; /** * Trigger by context menu click event @@ -31,7 +32,7 @@ export interface IEditorTreeController extends Partial { */ readonly onContextMenu?: ( menu: IMenuItemProps, - groupId: number, + groupId: UniqueId, file?: ITabProps ) => void; } @@ -88,7 +89,7 @@ export class EditorTreeController public onContextMenu = ( menu: IMenuItemProps, - groupId: number, + groupId: UniqueId, file?: ITabProps ) => { const { @@ -121,23 +122,26 @@ export class EditorTreeController } }; - public onClose = (tabId: string, groupId: number) => { + public onClose = (tabId: UniqueId, groupId: UniqueId) => { this.emit(EditorTreeEvent.onClose, tabId, groupId); }; - public onSelect = (tabId: string, groupId: number) => { + public onSelect = (tabId: UniqueId, groupId: UniqueId) => { this.emit(EditorTreeEvent.onSelect, tabId, groupId); }; - public onCloseGroup = (groupId: number) => { + public onCloseGroup = (groupId: UniqueId) => { this.emit(EditorTreeEvent.onCloseAll, groupId); }; - public onSaveGroup = (groupId: number) => { + public onSaveGroup = (groupId: UniqueId) => { this.emit(EditorTreeEvent.onSaveAll, groupId); }; - public onToolbarClick = (toolbar: IActionBarItemProps, groupId: number) => { + public onToolbarClick = ( + toolbar: IActionBarItemProps, + groupId: UniqueId + ) => { this.emit(EditorTreeEvent.onToolbarClick, toolbar, groupId); }; } diff --git a/src/controller/explorer/folderTree.tsx b/src/controller/explorer/folderTree.tsx index 9ddfa4307..d857e3af2 100644 --- a/src/controller/explorer/folderTree.tsx +++ b/src/controller/explorer/folderTree.tsx @@ -15,9 +15,10 @@ import { IBuiltinService, IFolderTreeService, } from 'mo/services'; +import type { UniqueId } from 'mo/common/types'; export interface IFolderTreeController extends Partial { - readonly createTreeNode?: (type: FileType) => void; + readonly createTreeNode?: (type: FileType, id?: UniqueId) => void; readonly onClickContextMenu?: ( contextMenu: IMenuItemProps, treeNode?: IFolderTreeNodeProps @@ -101,12 +102,19 @@ export class FolderTreeController }); } - public createTreeNode = (type: FileType) => { - const folderTreeState = this.folderTreeService.getState(); - const { data, current } = folderTreeState?.folderTree || {}; - // The current selected node id or the first root node - const nodeId = current?.id || data?.[0]?.id; - this.emit(FolderTreeEvent.onCreate, type, nodeId); + public createTreeNode = (type: FileType, id?: UniqueId) => { + if (typeof id === 'undefined') { + const folderTreeState = this.folderTreeService.getState(); + const { data, current } = folderTreeState?.folderTree || {}; + // The current selected node id or the first root node + const nodeId = + typeof current?.id === 'undefined' + ? data?.[0]?.id + : current?.id; + this.emit(FolderTreeEvent.onCreate, type, nodeId); + } else { + this.emit(FolderTreeEvent.onCreate, type, id); + } }; public readonly onClickContextMenu = ( @@ -133,11 +141,13 @@ export class FolderTreeController break; } case NEW_FILE_COMMAND_ID: { - this.createTreeNode(FileTypes.File); + const { id } = treeNode!; + this.createTreeNode(FileTypes.File, id); break; } case NEW_FOLDER_COMMAND_ID: { - this.createTreeNode(FileTypes.Folder); + const { id } = treeNode!; + this.createTreeNode(FileTypes.Folder, id); break; } case OPEN_TO_SIDE_COMMAND_ID: { diff --git a/src/controller/menuBar.ts b/src/controller/menuBar.ts index 501e8abca..d288d455a 100644 --- a/src/controller/menuBar.ts +++ b/src/controller/menuBar.ts @@ -15,9 +15,10 @@ import { ID_APP, ID_SIDE_BAR } from 'mo/common/id'; import { IMonacoService, MonacoService } from 'mo/monaco/monacoService'; import { CommandQuickSideBarViewAction } from 'mo/monaco/quickToggleSideBarAction'; import { QuickTogglePanelAction } from 'mo/monaco/quickTogglePanelAction'; +import type { UniqueId } from 'mo/common/types'; export interface IMenuBarController extends Partial { - onSelect?: (key: string, item?: IActivityBarItem) => void; + onSelect?: (key: UniqueId, item?: IActivityBarItem) => void; onClick: (event: React.MouseEvent, item: IMenuBarItem) => void; updateFocusinEle?: (ele: HTMLElement | null) => void; updateStatusBar?: () => void; diff --git a/src/controller/notification.tsx b/src/controller/notification.tsx index 1330943c2..ddbe21cda 100644 --- a/src/controller/notification.tsx +++ b/src/controller/notification.tsx @@ -1,23 +1,17 @@ import 'reflect-metadata'; import { container, singleton } from 'tsyringe'; import React from 'react'; -import ReactDOM from 'react-dom'; import { connect } from 'mo/react'; import { Float, IStatusBarItem } from 'mo/model'; import { Controller } from 'mo/react/controller'; import { IActionBarItemProps } from 'mo/components/actionBar'; import { INotificationItem } from 'mo/model/notification'; -import { - NotificationPane, - NotificationStatusBarView, -} from 'mo/workbench/notification'; +import { NotificationStatusBarView } from 'mo/workbench/notification'; import { IStatusBarService, StatusBarService, INotificationService, NotificationService, - ILayoutService, - LayoutService, IBuiltinService, BuiltinService, } from 'mo/services'; @@ -41,29 +35,20 @@ export class NotificationController implements INotificationController { private readonly notificationService: INotificationService; private readonly statusBarService: IStatusBarService; - private readonly layoutService: ILayoutService; private readonly builtinService: IBuiltinService; constructor() { super(); this.notificationService = container.resolve(NotificationService); this.statusBarService = container.resolve(StatusBarService); - this.layoutService = container.resolve(LayoutService); this.builtinService = container.resolve(BuiltinService); } public onCloseNotification = (item: INotificationItem): void => { - if (typeof item.id === 'number') { - this.notificationService.remove(item.id); - } + this.notificationService.remove(item.id); }; - private _notificationPane: HTMLDivElement | undefined = undefined; - public toggleNotifications() { - if (!this._notificationPane) { - this.renderNotificationPane(); - } this.notificationService.toggleNotification(); } @@ -82,7 +67,7 @@ export class NotificationController } = this.builtinService.getConstants(); if (action === NOTIFICATION_CLEAR_ALL_ID) { - this.notificationService.toggleNotification(); + this.notificationService.clear(); } else if (action === NOTIFICATION_HIDE_ID) { this.toggleNotifications(); } @@ -100,12 +85,19 @@ export class NotificationController this.notificationService, NotificationStatusBarView ); + /* istanbul ignore next */ const defaultNotification = { ...builtInNotification, actionBar: [NOTIFICATION_CLEAR_ALL, NOTIFICATION_HIDE].filter( Boolean ) as IActionBarItemProps[], - render: () => , + render: () => ( + + ), }; this.notificationService.setState({ ...defaultNotification, @@ -113,22 +105,4 @@ export class NotificationController this.statusBarService.add(defaultNotification, Float.right); } } - - public renderNotificationPane() { - const NotificationPaneView = connect( - this.notificationService, - NotificationPane - ); - const root = this.layoutService.container; - const container = document.createElement('div'); - root?.appendChild(container); - ReactDOM.render( - , - container - ); - this._notificationPane = container; - } } diff --git a/src/controller/panel.tsx b/src/controller/panel.tsx index ddccdd688..a3c71635d 100644 --- a/src/controller/panel.tsx +++ b/src/controller/panel.tsx @@ -13,11 +13,12 @@ import { import { IMonacoService, MonacoService } from 'mo/monaco/monacoService'; import { QuickTogglePanelAction } from 'mo/monaco/quickTogglePanelAction'; import Output from 'mo/workbench/panel/output'; +import type { UniqueId } from 'mo/common/types'; export interface IPanelController extends Partial { - onTabChange?(key: string | undefined): void; + onTabChange?(key: UniqueId): void; onToolbarClick?(e: React.MouseEvent, item: IActionBarItemProps): void; - onClose?(key?: string): void; + onClose?(key: UniqueId): void; } @singleton() @@ -64,7 +65,7 @@ export class PanelController extends Controller implements IPanelController { }); } - public readonly onTabChange = (key: string | undefined): void => { + public readonly onTabChange = (key: UniqueId): void => { const state = this.panelService.getState(); if (key) { this.panelService.setState({ @@ -74,7 +75,7 @@ export class PanelController extends Controller implements IPanelController { this.emit(PanelEvent.onTabChange, key); }; - public readonly onClose = (key?: string) => { + public readonly onClose = (key: UniqueId) => { if (key) { this.emit(PanelEvent.onTabClose, key); } diff --git a/src/controller/settings.tsx b/src/controller/settings.tsx index 12d81afc8..7bef0b0ea 100644 --- a/src/controller/settings.tsx +++ b/src/controller/settings.tsx @@ -67,7 +67,9 @@ export class SettingsController } private notifyLocaleChanged(prev: ILocale, next: ILocale) { + const { SETTING_ID } = this.builtinService.getConstants(); const notify = { + id: SETTING_ID!, value: next, render(value) { return ; diff --git a/src/extensions/theme-defaults/themes/dark_defaults.json b/src/extensions/theme-defaults/themes/dark_defaults.json index 626223e7c..1adfdb8f4 100644 --- a/src/extensions/theme-defaults/themes/dark_defaults.json +++ b/src/extensions/theme-defaults/themes/dark_defaults.json @@ -36,6 +36,7 @@ "minimapSlider.activeBackground": "#bfbfbf66", "minimapSlider.background": "#79797980", "minimapSlider.hoverBackground": "#64646480", + "molecule.welcomeBackground": "#393939", "panelTitle.activeForeground": "#E7E7E7", "panelTitle.inactiveForeground": "#e7e7e799", "problemsErrorIcon.foreground": "#F48771", diff --git a/src/extensions/theme-defaults/themes/light_defaults.json b/src/extensions/theme-defaults/themes/light_defaults.json index 759da81ff..6e1f78f09 100644 --- a/src/extensions/theme-defaults/themes/light_defaults.json +++ b/src/extensions/theme-defaults/themes/light_defaults.json @@ -33,6 +33,7 @@ "minimapSlider.activeBackground": "#00000099", "minimapSlider.background": "#64646480", "minimapSlider.hoverBackground": "#64646480", + "molecule.welcomeBackground": "#fff", "panelTitle.activeForeground": "#424242", "panelTitle.inactiveForeground": "#424242bf", "problemsErrorIcon.foreground": "#E51400", diff --git a/src/i18n/index.ts b/src/i18n/index.ts index b1ee65df4..9d6556617 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,3 +1,5 @@ export { LocaleService } from './localeService'; export type { ILocaleService } from './localeService'; export type { ILocale } from './localization'; +export type { ILocalizeProps } from './localize'; +export { localize } from './localize'; diff --git a/src/i18n/selectLocaleAction.ts b/src/i18n/selectLocaleAction.ts index b83e72c54..8d7cbee89 100644 --- a/src/i18n/selectLocaleAction.ts +++ b/src/i18n/selectLocaleAction.ts @@ -33,7 +33,7 @@ export class SelectLocaleAction extends Action2 { f1: true, keybinding: { when: undefined, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL, }, }); } diff --git a/src/model/extension.ts b/src/model/extension.ts index 02de228cd..b3ef3a3cd 100644 --- a/src/model/extension.ts +++ b/src/model/extension.ts @@ -1,3 +1,4 @@ +import type { UniqueId } from 'mo/common/types'; import { IExtensionService } from 'mo/services'; import { IColorTheme } from './colorTheme'; import { IIconTheme } from './iconTheme'; @@ -41,7 +42,7 @@ export interface IExtension { /** * The ID of extension required */ - id: string; + id: UniqueId; /** * The name of extension */ diff --git a/src/model/keybinding.ts b/src/model/keybinding.ts index cecbf0cb2..069f85ecb 100644 --- a/src/model/keybinding.ts +++ b/src/model/keybinding.ts @@ -7,53 +7,53 @@ export const KeyCodeString: Partial<{ [key in KeyCode]: string }> = { [KeyCode.Enter]: '↩︎', [KeyCode.PageUp]: '↑', [KeyCode.PageDown]: '↓', - [KeyCode.KEY_0]: '0', - [KeyCode.KEY_1]: '1', - [KeyCode.KEY_2]: '2', - [KeyCode.KEY_3]: '3', - [KeyCode.KEY_4]: '4', - [KeyCode.KEY_5]: '5', - [KeyCode.KEY_6]: '6', - [KeyCode.KEY_7]: '7', - [KeyCode.KEY_8]: '8', - [KeyCode.KEY_9]: '9', - [KeyCode.KEY_A]: 'A', - [KeyCode.KEY_B]: 'B', - [KeyCode.KEY_C]: 'C', - [KeyCode.KEY_D]: 'D', - [KeyCode.KEY_E]: 'E', - [KeyCode.KEY_F]: 'F', - [KeyCode.KEY_G]: 'G', - [KeyCode.KEY_H]: 'H', - [KeyCode.KEY_I]: 'I', - [KeyCode.KEY_J]: 'J', - [KeyCode.KEY_K]: 'K', - [KeyCode.KEY_L]: 'L', - [KeyCode.KEY_M]: 'M', - [KeyCode.KEY_N]: 'N', - [KeyCode.KEY_O]: 'O', - [KeyCode.KEY_P]: 'P', - [KeyCode.KEY_Q]: 'Q', - [KeyCode.KEY_R]: 'R', - [KeyCode.KEY_S]: 'S', - [KeyCode.KEY_T]: 'T', - [KeyCode.KEY_U]: 'U', - [KeyCode.KEY_V]: 'V', - [KeyCode.KEY_W]: 'W', - [KeyCode.KEY_X]: 'X', - [KeyCode.KEY_Y]: 'Y', - [KeyCode.KEY_Z]: 'Z', - [KeyCode.US_SEMICOLON]: ';', - [KeyCode.US_EQUAL]: '+', - [KeyCode.US_COMMA]: ',', - [KeyCode.US_MINUS]: '-', - [KeyCode.US_DOT]: '.', - [KeyCode.US_SLASH]: '/', - [KeyCode.US_BACKTICK]: '~', - [KeyCode.US_OPEN_SQUARE_BRACKET]: '[', - [KeyCode.US_BACKSLASH]: '\\', - [KeyCode.US_CLOSE_SQUARE_BRACKET]: ']', - [KeyCode.US_QUOTE]: '"', + [KeyCode.Digit0]: '0', + [KeyCode.Digit1]: '1', + [KeyCode.Digit2]: '2', + [KeyCode.Digit3]: '3', + [KeyCode.Digit4]: '4', + [KeyCode.Digit5]: '5', + [KeyCode.Digit6]: '6', + [KeyCode.Digit7]: '7', + [KeyCode.Digit8]: '8', + [KeyCode.Digit9]: '9', + [KeyCode.KeyA]: 'A', + [KeyCode.KeyB]: 'B', + [KeyCode.KeyC]: 'C', + [KeyCode.KeyD]: 'D', + [KeyCode.KeyE]: 'E', + [KeyCode.KeyF]: 'F', + [KeyCode.KeyG]: 'G', + [KeyCode.KeyH]: 'H', + [KeyCode.KeyI]: 'I', + [KeyCode.KeyJ]: 'J', + [KeyCode.KeyK]: 'K', + [KeyCode.KeyL]: 'L', + [KeyCode.KeyM]: 'M', + [KeyCode.KeyN]: 'N', + [KeyCode.KeyO]: 'O', + [KeyCode.KeyP]: 'P', + [KeyCode.KeyQ]: 'Q', + [KeyCode.KeyR]: 'R', + [KeyCode.KeyS]: 'S', + [KeyCode.KeyT]: 'T', + [KeyCode.KeyU]: 'U', + [KeyCode.KeyV]: 'V', + [KeyCode.KeyW]: 'W', + [KeyCode.KeyX]: 'X', + [KeyCode.KeyY]: 'Y', + [KeyCode.KeyZ]: 'Z', + [KeyCode.Semicolon]: ';', + [KeyCode.Equal]: '+', + [KeyCode.Comma]: ',', + [KeyCode.Minus]: '-', + [KeyCode.Period]: '.', + [KeyCode.Slash]: '/', + [KeyCode.Backquote]: '~', + [KeyCode.BracketLeft]: '[', + [KeyCode.Backslash]: '\\', + [KeyCode.BracketRight]: ']', + [KeyCode.Quote]: '"', }; export interface ISimpleKeybinding { diff --git a/src/model/notification.tsx b/src/model/notification.tsx index 1fa23eead..8dd391147 100644 --- a/src/model/notification.tsx +++ b/src/model/notification.tsx @@ -1,6 +1,7 @@ +import { UniqueId } from 'mo/common/types'; import { IActionBarItemProps } from 'mo/components/actionBar'; import React from 'react'; -import { IStatusBarItem } from './workbench/statusBar'; +import type { IStatusBarItem } from './workbench/statusBar'; export enum NotificationStatus { Read = 1, @@ -8,20 +9,20 @@ export enum NotificationStatus { } export interface INotificationItem { - id?: number; + id: UniqueId; value: T; render?(item: INotificationItem): React.ReactNode; status?: NotificationStatus; } -export interface INotification extends IStatusBarItem { - data?: INotificationItem[]; +export interface INotification + extends IStatusBarItem[]> { showNotifications?: boolean; actionBar?: IActionBarItemProps[]; } export class NotificationModel implements INotification { - public id: string; + public id: UniqueId; public name: string; public data: INotificationItem[]; public sortIndex: number; @@ -30,7 +31,7 @@ export class NotificationModel implements INotification { public actionBar: IActionBarItemProps[]; constructor( - id: string = '', + id: UniqueId = '', name: string = '', data: INotificationItem[] = [], sortIndex: number = 1, diff --git a/src/model/problems.tsx b/src/model/problems.tsx index 603773529..4c8f907d3 100644 --- a/src/model/problems.tsx +++ b/src/model/problems.tsx @@ -1,3 +1,4 @@ +import type { UniqueId } from 'mo/common/types'; import { ITreeNodeItemProps } from 'mo/components'; export enum MarkerSeverity { @@ -21,20 +22,20 @@ export interface IProblemsItem extends ITreeNodeItemProps { } export interface IProblems { - id: string; + id: UniqueId; name: string; data: IProblemsItem[]; show?: boolean; } export class ProblemsModel implements IProblems { - public id: string; + public id: UniqueId; public name: string; public data: IProblemsItem[]; public show: boolean; constructor( - id: string = '', + id: UniqueId = '', name: string = '', data: IProblemsItem[] = [], show: boolean = false diff --git a/src/model/workbench/activityBar.ts b/src/model/workbench/activityBar.ts index 361d0d940..f2b31ddf0 100644 --- a/src/model/workbench/activityBar.ts +++ b/src/model/workbench/activityBar.ts @@ -1,5 +1,6 @@ import React from 'react'; import { IMenuItemProps } from 'mo/components'; +import type { HTMLElementProps, UniqueId } from 'mo/common/types'; /** * The activity bar event definition */ @@ -13,10 +14,9 @@ export enum ActivityBarEvent { ReRender = 'activityBar.reRender', } -export interface IActivityBarItem { - id: string; +export interface IActivityBarItem extends HTMLElementProps { + id: UniqueId; name?: React.ReactNode; - title?: string; hidden?: boolean; data?: any; icon?: string | JSX.Element; @@ -24,29 +24,28 @@ export interface IActivityBarItem { disabled?: boolean; type?: 'normal' | 'global'; contextMenu?: IActivityMenuItemProps[]; - className?: string; sortIndex?: number; render?: () => React.ReactNode | JSX.Element; } export interface IActivityMenuItemProps extends IMenuItemProps { - id: string; + id: UniqueId; } export interface IActivityBar { data?: IActivityBarItem[]; contextMenu?: IActivityMenuItemProps[]; - selected?: string; + selected?: UniqueId; } export class ActivityBarModel implements IActivityBar { public data: IActivityBarItem[]; public contextMenu: IActivityMenuItemProps[]; - public selected: string; + public selected: UniqueId; constructor( data: IActivityBarItem[] = [], contextMenu: IActivityMenuItemProps[] = [], - selected: string = '' + selected: UniqueId = '' ) { this.data = data; this.contextMenu = contextMenu; diff --git a/src/model/workbench/editor.ts b/src/model/workbench/editor.ts index 7dd558fc6..f07f329d9 100644 --- a/src/model/workbench/editor.ts +++ b/src/model/workbench/editor.ts @@ -3,6 +3,7 @@ import { ITabsProps } from 'mo/components/tabs'; import { IMenuItemProps } from 'mo/components/menu'; import { IBreadcrumbItemProps } from 'mo/components/breadcrumb'; import { editor as MonacoEditor } from 'monaco-editor'; +import type { UniqueId } from 'mo/common/types'; export enum EditorEvent { OnCloseTab = 'editor.closeTab', @@ -29,7 +30,7 @@ export type IEditorOptions = MonacoEditor.IEditorOptions & MonacoEditor.IGlobalEditorOptions; export interface IEditorActionsProps extends IMenuItemProps { - id: string; + id: UniqueId; /** * Mark the action placed in More menus or outer */ @@ -44,7 +45,7 @@ export interface IEditorAction { menu?: IMenuItemProps[]; } export interface IEditorGroup extends ITabsProps { - id?: number; + id: UniqueId; /** * Current editor group tab */ @@ -73,18 +74,18 @@ export interface IEditor { } export class EditorGroupModel implements IEditorGroup { - id: number; + id: UniqueId; tab: IEditorTab; data: IEditorTab[]; actions: IEditorActionsProps[]; menu: IMenuItemProps[]; editorInstance: E | undefined; - activeTab: string | undefined; + activeTab: UniqueId | undefined; constructor( - id: number, + id: UniqueId, tab: IEditorTab, - activeTab: string | undefined, + activeTab: UniqueId | undefined, data: IEditorTab[], actions: IEditorActionsProps[] = [], menu: IMenuItemProps[] = [], diff --git a/src/model/workbench/explorer/folderTree.tsx b/src/model/workbench/explorer/folderTree.tsx index 9eb2b72a7..33a23f6c7 100644 --- a/src/model/workbench/explorer/folderTree.tsx +++ b/src/model/workbench/explorer/folderTree.tsx @@ -2,6 +2,7 @@ import React from 'react'; import 'reflect-metadata'; import type { ITreeNodeItemProps } from 'mo/components/tree'; import type { IMenuItemProps } from 'mo/components/menu'; +import type { UniqueId } from 'mo/common/types'; export enum FileTypes { File = 'File', diff --git a/src/model/workbench/menuBar.ts b/src/model/workbench/menuBar.ts index b33827bb3..343065b97 100644 --- a/src/model/workbench/menuBar.ts +++ b/src/model/workbench/menuBar.ts @@ -1,6 +1,7 @@ import React from 'react'; import { ISubMenuProps } from 'mo/components/menu/subMenu'; import { IMenuItemProps } from 'mo/components/menu'; +import type { UniqueId } from 'mo/common/types'; /** * The activity bar event definition */ @@ -12,7 +13,7 @@ export enum MenuBarEvent { } export interface IMenuBarItem { - id?: string; + id?: UniqueId; name?: string; icon?: string | JSX.Element; data?: ISubMenuProps[]; diff --git a/src/model/workbench/panel.tsx b/src/model/workbench/panel.tsx index cd46866e2..20b167109 100644 --- a/src/model/workbench/panel.tsx +++ b/src/model/workbench/panel.tsx @@ -2,7 +2,6 @@ import { editor as MonacoEditor } from 'mo/monaco'; import { IActionBarItemProps } from 'mo/components/actionBar'; import { ITabProps } from 'mo/components/tabs/tab'; export interface IPanelItem extends ITabProps { - id: string; /** * The same as HTMLElement title attribute */ diff --git a/src/model/workbench/sidebar.ts b/src/model/workbench/sidebar.ts index b6f04e2ff..5468850c5 100644 --- a/src/model/workbench/sidebar.ts +++ b/src/model/workbench/sidebar.ts @@ -1,19 +1,21 @@ +import type { UniqueId } from 'mo/common/types'; + export interface ISidebarPane { - id: string; + id: UniqueId; title?: string; render?: () => React.ReactNode; } export interface ISidebar { - current: string; + current: UniqueId; panes: ISidebarPane[]; } export class SidebarModel implements ISidebar { - public current: string; + public current: UniqueId; public panes: ISidebarPane[]; - constructor(panes: ISidebarPane[] = [], selected: string = '') { + constructor(panes: ISidebarPane[] = [], selected: UniqueId = '') { this.panes = panes; this.current = selected; } diff --git a/src/model/workbench/statusBar.tsx b/src/model/workbench/statusBar.tsx index 60b0d4050..28d605174 100644 --- a/src/model/workbench/statusBar.tsx +++ b/src/model/workbench/statusBar.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { IMenuItemProps } from 'mo/components/menu'; +import type { HTMLElementProps, UniqueId } from 'mo/common/types'; export enum Float { left = 'left', @@ -7,7 +8,7 @@ export enum Float { } export interface IStatusBarItem extends HTMLElementProps { - id: string; + id: UniqueId; sortIndex?: number; data?: T; onClick?(e: React.MouseEvent, item?: IStatusBarItem); diff --git a/src/molecule.api.ts b/src/molecule.api.ts index 169eb3dcc..b5c31e531 100644 --- a/src/molecule.api.ts +++ b/src/molecule.api.ts @@ -4,18 +4,11 @@ import { container } from 'tsyringe'; export * as event from 'mo/common/event'; export * as react from 'mo/react'; export * as component from 'mo/components'; -export * as i18n from 'mo/i18n'; +export * from 'mo/i18n'; export * from 'mo/workbench'; export * from 'mo/services'; -export { - IExtension, - IColorTheme, - ISettings, - IColors, - TokenColor, - ColorScheme, -} from 'mo/model'; +export * as models from 'mo/model'; import { ILayoutService, @@ -58,7 +51,7 @@ import { ILocaleService, LocaleService } from 'mo/i18n'; /** * The locale service */ -export const il8n = container.resolve(LocaleService); +export const i18n = container.resolve(LocaleService); /** * The layout service diff --git a/src/monaco/quickAccessSettingsAction.ts b/src/monaco/quickAccessSettingsAction.ts index 7391d63f6..3646542db 100644 --- a/src/monaco/quickAccessSettingsAction.ts +++ b/src/monaco/quickAccessSettingsAction.ts @@ -28,7 +28,7 @@ export class QuickAccessSettings extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_COMMA), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.Comma), }, }); this.settingsService = container.resolve(SettingsService); diff --git a/src/monaco/quickAccessViewAction.ts b/src/monaco/quickAccessViewAction.ts index e9d2e70c1..7d4e0e18f 100644 --- a/src/monaco/quickAccessViewAction.ts +++ b/src/monaco/quickAccessViewAction.ts @@ -184,7 +184,7 @@ export class CommandQuickAccessViewAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: undefined, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyP, secondary: [KeyCode.F1], }, }); diff --git a/src/monaco/quickCreateFile.ts b/src/monaco/quickCreateFile.ts index 92849ec72..de91ddad1 100644 --- a/src/monaco/quickCreateFile.ts +++ b/src/monaco/quickCreateFile.ts @@ -31,7 +31,7 @@ export class QuickCreateFile extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyMod.CtrlCmd | KeyCode.KEY_N, + primary: KeyMod.CtrlCmd | KeyCode.KeyN, }, }); this.folderTreeController = container.resolve(FolderTreeController); diff --git a/src/monaco/quickRedo.ts b/src/monaco/quickRedo.ts index c2424ece4..47f37a093 100644 --- a/src/monaco/quickRedo.ts +++ b/src/monaco/quickRedo.ts @@ -26,7 +26,7 @@ export class QuickRedo extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyZ, }, }); this.editorService = container.resolve(EditorService); @@ -59,7 +59,9 @@ export class QuickRedo extends Action2 { currentActiveGroup.id! ); editorInstance?.focus(); - const model = MonacoEditor.getModel(Uri.parse(tab!.id!))!; + const model = MonacoEditor.getModel( + Uri.parse(tab!.id!.toString()) + )!; (model as any).redo(); } } diff --git a/src/monaco/quickSelectAllAction.ts b/src/monaco/quickSelectAllAction.ts index f76a64530..a6022d435 100644 --- a/src/monaco/quickSelectAllAction.ts +++ b/src/monaco/quickSelectAllAction.ts @@ -26,7 +26,7 @@ export class QuickSelectAllAction extends Action2 { keybinding: { weight: KeybindingWeight.BuiltinExtension, when: undefined, - primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + primary: KeyMod.CtrlCmd | KeyCode.KeyA, }, }); this.editorService = container.resolve(EditorService); diff --git a/src/monaco/quickTogglePanelAction.ts b/src/monaco/quickTogglePanelAction.ts index 0c351a69d..dda90e363 100644 --- a/src/monaco/quickTogglePanelAction.ts +++ b/src/monaco/quickTogglePanelAction.ts @@ -30,7 +30,7 @@ export class QuickTogglePanelAction extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_J), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyJ), }, }); this.layoutService = container.resolve(LayoutService); diff --git a/src/monaco/quickToggleSideBarAction.ts b/src/monaco/quickToggleSideBarAction.ts index 90b86fe38..13cb3d897 100644 --- a/src/monaco/quickToggleSideBarAction.ts +++ b/src/monaco/quickToggleSideBarAction.ts @@ -15,6 +15,7 @@ import { SidebarService, } from 'mo/services'; import { ID_SIDE_BAR } from 'mo/common/id'; +import type { UniqueId } from 'mo/common/types'; export class CommandQuickSideBarViewAction extends Action2 { static readonly ID = ID_SIDE_BAR; @@ -26,7 +27,7 @@ export class CommandQuickSideBarViewAction extends Action2 { private readonly activityBarService: IActivityBarService; private readonly menuBarService: IMenuBarService; private readonly sideBarService: ISidebarService; - private _preActivityBar: string | undefined; + private _preActivityBar: UniqueId | undefined; constructor() { super({ @@ -41,7 +42,7 @@ export class CommandQuickSideBarViewAction extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_B), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyB), }, }); this.layoutService = container.resolve(LayoutService); diff --git a/src/monaco/quickUndo.ts b/src/monaco/quickUndo.ts index a6e454164..bbae1c350 100644 --- a/src/monaco/quickUndo.ts +++ b/src/monaco/quickUndo.ts @@ -26,7 +26,7 @@ export class QuickUndo extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, + primary: KeyMod.CtrlCmd | KeyCode.KeyZ, }, }); this.editorService = container.resolve(EditorService); @@ -59,7 +59,9 @@ export class QuickUndo extends Action2 { currentActiveGroup.id! ); editorInstance?.focus(); - const model = MonacoEditor.getModel(Uri.parse(tab!.id!))!; + const model = MonacoEditor.getModel( + Uri.parse(tab!.id!.toString()) + )!; (model as any).undo(); } } diff --git a/src/monaco/selectColorThemeAction.ts b/src/monaco/selectColorThemeAction.ts index 108eefe20..2f7cb0f2b 100644 --- a/src/monaco/selectColorThemeAction.ts +++ b/src/monaco/selectColorThemeAction.ts @@ -30,7 +30,7 @@ export class SelectColorThemeAction extends Action2 { when: undefined, weight: KeybindingWeight.WorkbenchContrib, // eslint-disable-next-line new-cap - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK), }, }); this.colorThemeService = container.resolve(ColorThemeService); diff --git a/src/provider/__tests__/__snapshots__/molecule.test.tsx.snap b/src/provider/__tests__/__snapshots__/molecule.test.tsx.snap new file mode 100644 index 000000000..c51b566ce --- /dev/null +++ b/src/provider/__tests__/__snapshots__/molecule.test.tsx.snap @@ -0,0 +1,780 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test MoleculeProvider Match The MoleculeProvider snapshot 1`] = ` +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
      +
    • + +
      +
    • +
    • + +
    • +
    +
      +
    • + +
    • +
    • +
      + +
      +
    • +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Explorer +

    +
    +
    +
    +
    +
      +
    • + +
    • +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + Open Editors + +
    +
    +
    +
    +
    +
    + + + No Open Folder + +
    +
    +
    +
    +
    + you have not yet opened a folder + + Add Folder + +
    +
    +
    +
    +
    +
    + + + Outline + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Search +

    +
    +
    +
    +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    +
    +
    +
    + +
    +
    +