From e9d8d3dbe698a4f3b3dad736ac959dd581bd01b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E7=A6=85?= Date: Wed, 9 Sep 2020 21:53:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=84=E4=BB=B6=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=9A=E5=8A=A1=E7=BB=84=E4=BB=B6=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/demo/public/assets.json | 50 ++-- packages/plugin-components-pane/package.json | 1 - .../src/components/base/index.js | 270 +++++++++++++++++ .../src/components/base/index.less | 116 ++++++++ .../src/components/button/index.js | 22 ++ .../src/components/button/index.less | 18 ++ .../src/components/card/index.js | 147 +++++++++ .../src/components/card/index.less | 130 ++++++++ .../src/components/component-list/index.js | 279 ++++++++++++++++++ .../src/components/component-list/index.less | 117 ++++++++ .../src/components/snippet/index.js | 46 +++ .../src/components/snippet/index.less | 120 ++++++++ .../plugin-components-pane/src/i18n/index.js | 45 +++ .../src/i18n/strings/en-US.json | 27 ++ .../src/i18n/strings/index.js | 4 + .../src/i18n/strings/zh-CN.json | 26 ++ packages/plugin-components-pane/src/index.tsx | 34 ++- packages/plugin-components-pane/src/utils.js | 99 +++++++ 18 files changed, 1518 insertions(+), 33 deletions(-) create mode 100644 packages/plugin-components-pane/src/components/base/index.js create mode 100644 packages/plugin-components-pane/src/components/base/index.less create mode 100644 packages/plugin-components-pane/src/components/button/index.js create mode 100644 packages/plugin-components-pane/src/components/button/index.less create mode 100644 packages/plugin-components-pane/src/components/card/index.js create mode 100644 packages/plugin-components-pane/src/components/card/index.less create mode 100644 packages/plugin-components-pane/src/components/component-list/index.js create mode 100644 packages/plugin-components-pane/src/components/component-list/index.less create mode 100644 packages/plugin-components-pane/src/components/snippet/index.js create mode 100644 packages/plugin-components-pane/src/components/snippet/index.less create mode 100644 packages/plugin-components-pane/src/i18n/index.js create mode 100644 packages/plugin-components-pane/src/i18n/strings/en-US.json create mode 100644 packages/plugin-components-pane/src/i18n/strings/index.js create mode 100644 packages/plugin-components-pane/src/i18n/strings/zh-CN.json create mode 100644 packages/plugin-components-pane/src/utils.js diff --git a/packages/demo/public/assets.json b/packages/demo/public/assets.json index 24b3061c6..ef8650e05 100644 --- a/packages/demo/public/assets.json +++ b/packages/demo/public/assets.json @@ -13107,30 +13107,6 @@ } ], "componentList": [ - { - "title": "Util", - "children": [ - { - "componentName": "Dropdown", - "title": "下拉菜单", - "icon": "", - "package": "@alifd/next", - "library": "Next", - "snippets": [ - { - "title": "下拉菜单", - "screenshot": "", - "schema": { - "componentName": "Dropdown", - "props": { "trigger": [{ "componentName": "Button", "props": { "type": "primary" }, "children": "确定" }], "triggerType": "click"}, - "children": [{ "componentName": "Menu", "props": { "style": { "width": 200 } }, "children": [{ "componentName": "Menu.Item", "props": {}, "children": "Option 1" }, { "componentName": "Menu.Item", "props": { "disabled": false }, "children": "option 2" }, { "componentName": "Menu.Item", "props": { "disabled": false }, "children": "option 3" }]}] - } - } - ] - } - ], - "icon": "" - }, { "title": "DataDisplay", "children": [ @@ -15388,5 +15364,31 @@ ], "icon": "" } + ], + "bizComponentList": [ + { + "title": "Util", + "children": [ + { + "componentName": "Dropdown", + "title": "下拉菜单", + "icon": "", + "package": "@alifd/next", + "library": "Next", + "snippets": [ + { + "title": "下拉菜单", + "screenshot": "", + "schema": { + "componentName": "Dropdown", + "props": { "trigger": [{ "componentName": "Button", "props": { "type": "primary" }, "children": "确定" }], "triggerType": "click"}, + "children": [{ "componentName": "Menu", "props": { "style": { "width": 200 } }, "children": [{ "componentName": "Menu.Item", "props": {}, "children": "Option 1" }, { "componentName": "Menu.Item", "props": { "disabled": false }, "children": "option 2" }, { "componentName": "Menu.Item", "props": { "disabled": false }, "children": "option 3" }]}] + } + } + ] + } + ], + "icon": "" + } ] } diff --git a/packages/plugin-components-pane/package.json b/packages/plugin-components-pane/package.json index 47686b482..36060b79b 100644 --- a/packages/plugin-components-pane/package.json +++ b/packages/plugin-components-pane/package.json @@ -23,7 +23,6 @@ "@ali/lowcode-designer": "^1.0.8-0", "@ali/lowcode-editor-core": "^1.0.8-0", "@ali/lowcode-types": "^1.0.8-0", - "@ali/ve-component-list": "^1.1.1", "@alifd/next": "^1.19.19", "react": "^16.8.1" }, diff --git a/packages/plugin-components-pane/src/components/base/index.js b/packages/plugin-components-pane/src/components/base/index.js new file mode 100644 index 000000000..97283f518 --- /dev/null +++ b/packages/plugin-components-pane/src/components/base/index.js @@ -0,0 +1,270 @@ +import React from "react"; +import { Search, Box } from "@alifd/next"; +import Button from "../button/index.js"; +import Card from "../card"; +import $i18n from "../../i18n/index.js"; +import { searchComponent, builtinSearchMap } from "../../utils"; +import "./index.less"; + +/** + * 配置元素的操作类型 + * Draggable:可拖拽 + * Clickable:可点击 + * All:可拖拽也可点击 + */ +export const AdditiveType = { + Draggable: "additive-drag", + Clickable: "additive-click", + All: "additive", +}; + +class Base extends React.Component { + static propTypes = { + metaData: PropTypes.array, + className: PropTypes.string, + registerAdditive: PropTypes.func, + renderCustomSnippet: PropTypes.func, + actions: PropTypes.array, + getComponentInfo: PropTypes.func, + enableSearch: PropTypes.bool, + enableCard: PropTypes.bool, + enableReport: PropTypes.bool, + placeholder: PropTypes.string, + }; + + static defaultProps = { + metaData: [], + registerAdditive: () => { + return; + }, + className: "", + renderCustomSnippet: null, + actions: [], + getComponentInfo: null, + enableSearch: false, + enableCard: true, + enableReport: true, + placeholder: "", + }; + + state = { + selected: "", + searchText: "", + currentCard: null, + target: null, + currentCardImage: '', + }; + + shell = null; + isEmpty = false; + mode = 'advance'; + searchMap = {}; + timer = null; + isMouseEnterCard = false; + + + componentWillMount() { + // TODO get remote search map + this.searchMap = builtinSearchMap; + } + + hasActions() { + const { actions } = this.props; + if (!actions || !Array.isArray(actions) || !actions.length) { + return false; + } + return true; + } + + onSearch(val) { + this.setState({ + searchText: val, + currentCard: null, // 清空卡片 + }); + } + + normalizeBundle(mode) { + const { metaData } = this.props; + const { searchText = "" } = this.state; + const groupList = metaData.filter((comp, index) => { + const { title = "", componentName = "", id = "" } = comp; + if (!id) { + comp.id = `${comp.componentName}_${index}`; + } + const query = searchText.toLowerCase(); + return ( + !![title, componentName].find( + (it) => it.toLowerCase().indexOf(query) > -1 + ) || !!searchComponent(title, query, this.searchMap) + ); + }); + + if (mode === 'simple') { + return groupList; + } + + let bundle = {}; + // 按一定顺序排列 + groupList.forEach((m) => { + const c = m.category || "Others"; + if (!bundle[c]) { + bundle[c] = []; + } + bundle[c].push(m); + }); + return bundle; + } + + renderEmptyData() { + this.isEmpty = true; + return ( + + +
+
暂无组件,请在物料站点添加
+
+
+ ); + } + + renderHeader() { + const { placeholder } = this.props; + return ( + + ); + } + + renderActions() { + const { actions = [] } = this.props; + if (!this.hasActions()) { + return null; + } + const len = actions.length; + // TODO:len = 1:只有一个主按钮;len = 2:一个主按钮、一个次按钮;len >=3:一个主按钮、一个次按钮、其余放在按钮组里; + return actions.map((action, idx) => { + return ( + + ); + }); + } + + renderBundle() { + const { metaData } = this.props; + if (!metaData || !Array.isArray(metaData) || !metaData.length) { + return this.renderEmptyData(); + } + const bundle = this.normalizeBundle(this.mode); + if (!Object.keys(bundle).length || (Array.isArray(bundle) && bundle.length === 0)) { + return this.renderEmptyData(); + } + this.isEmpty = false; + return this.getBundle ? this.getBundle(bundle) : this.renderEmptyData(); + } + + renderCard() { + const { currentCard, target, currentCardImage } = this.state; + const { getComponentInfo } = this.props; + + if (!currentCard || !getComponentInfo) return null; + + return ( + { + this.setState({ currentCard: null }); + this.isMouseEnterCard = false; + }} + offset={{ + top: 10, + left: 10, + }} + onMouseEnter={() => { + this.isMouseEnterCard = true; + }} + /> + ); + } + + render() { + const { + enableSearch, + className, + registerAdditive = () => { + return; + }, + } = this.props; + let bodyExtraClass = ""; + if (this.hasActions() && enableSearch) { + bodyExtraClass = "small"; + } else if (!this.hasActions() && enableSearch) { + bodyExtraClass = "medium"; + } else if (this.hasActions() && !enableSearch) { + bodyExtraClass = "large"; + } else { + bodyExtraClass = ""; + } + + return ( +
+ {enableSearch ? ( +
+ {this.renderHeader()} +
+ ) : null} +
{ + if (this.shell || !shell || this.isEmpty) { + return; + } + if (!this.shell) { + this.shell = shell; + } + registerAdditive(shell); + }} + > + {this.renderBundle()} +
+
+ {this.renderActions()} +
+ {this.renderCard ? this.renderCard() : null} +
+ ); + } +} + +export default Base; diff --git a/packages/plugin-components-pane/src/components/base/index.less b/packages/plugin-components-pane/src/components/base/index.less new file mode 100644 index 000000000..d257a0cbe --- /dev/null +++ b/packages/plugin-components-pane/src/components/base/index.less @@ -0,0 +1,116 @@ +@import "~@ali/ve-less-variables/index.less"; + +.ve-component-list { + position: absolute; + top: 45px; + right: 0; + bottom: 0; + left: 0; + .ve-component-list-head { + height: 56px; + padding: 12px; + border-bottom: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1)); + .ve-search-control { + width: 256px; + height: 32px; + margin: 0; + } + } + .ve-component-list-body { + display: flex; + height: 100%; + overflow: auto; + .ve-component-list-empty { + width: 100%; + padding: 20px; + text-align: center; + &.kuma-empty-data.normal .kuma-empty-data-icon { + width: 50px; + height: 50px; + background-size: 100% 100%; + display: inline-block; + } + } + .component-description-item-icon-group { + display: flex; + justify-content: flex-start; + align-items: center; + .component-description-item-icon { + margin-left: 4px; + &.tag { + display: flex; + justify-content: center; + align-items: center; + background: rgba(249, 189, 15, 0.1); + color: #ff6f00; + font-size: 12px; + width: 48px; + height: 24px; + } + } + icon { + position: relative; + font-size: 14px; + cursor: pointer; + color: rgba(31, 56, 88, 0.4); + transition: color @transition-duration @transition-ease; + &:hover { + color: rgba(31, 56, 88, 0.6); + } + a { + position: absolute; + left: 0; + top: 0; + display: inline-block; + width: 14px; + height: 14px; + } + } + } + &.small { + height: calc(100% - 56px - 48px); + } + &.medium { + height: calc(100% - 56px); + } + &.large { + height: calc(100% - 48px); + } + } + .ve-component-list-foot { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + bottom: 0; + .btn { + margin-right: 8px; + flex: 1; + &:last-child { + margin-right: 0; + } + } + .kuma-button-primary { + background-color: var(--color-brand, @brand-color-1); + border-color: var(--color-brand, @brand-color-1); + border-radius: @global-border-radius; + color: var(--color-text-reverse, @white-alpha-2); + transition: background-color @transition-duration @transition-ease; + &:hover { + background-color: var(--color-brand-dark, @brand-color-3); + border-color: var(--color-brand-dark, @brand-color-3); + transition: background-color @transition-duration @transition-ease; + } + } + &.exist { + width: 100%; + height: 48px; + border-top: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1)); + background: #fff; + padding: 0 12px; + } + } + ::-webkit-scrollbar { + display: none; + } +} diff --git a/packages/plugin-components-pane/src/components/button/index.js b/packages/plugin-components-pane/src/components/button/index.js new file mode 100644 index 000000000..d1d385e22 --- /dev/null +++ b/packages/plugin-components-pane/src/components/button/index.js @@ -0,0 +1,22 @@ +import { Button } from "@ali/vu-uxcore-legao-design"; +import './index.less'; + +const MyButton = (props) => { + const { action, componentPrototype, type, className } = props; + return ( + + ); +}; + +export default MyButton; diff --git a/packages/plugin-components-pane/src/components/button/index.less b/packages/plugin-components-pane/src/components/button/index.less new file mode 100644 index 000000000..ef62922e6 --- /dev/null +++ b/packages/plugin-components-pane/src/components/button/index.less @@ -0,0 +1,18 @@ +.ve-component-list { + .kuma-button-outline { + border-color: #0079f2; + color: #0079f2; + &:hover { + border-color: #0079f2; + color: #0079f2; + background-color: #f0f7ff; + } + &.kuma-button-danger { + border-color: #f04631; + color: #f04631; + &:hover { + background-color: rgba(240, 70, 49, 0.06); + } + } + } +} diff --git a/packages/plugin-components-pane/src/components/card/index.js b/packages/plugin-components-pane/src/components/card/index.js new file mode 100644 index 000000000..04883bf1e --- /dev/null +++ b/packages/plugin-components-pane/src/components/card/index.js @@ -0,0 +1,147 @@ +import React from "react"; +import Layer from "@ali/vu-layer"; +import { Icon } from "@alifd/next"; +import $i18n from "../../i18n/index"; +import Button from "../button"; +import "./index.less"; + +export default class Card extends React.Component { + static propTypes = {}; + + constructor(props) { + super(props); + + this.state = { + isLoading: true, + errorMsg: false, + componentInfo: {}, + }; + } + + componentDidMount() { + if (!this.props.getComponentInfo) return; + this.loadComponentInfo(this.props.componentPrototype); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.componentPrototype !== this.props.componentPrototype) { + // 延迟执行数据加载和渲染,等 props 更新之后 + this.loadComponentInfo(nextProps.componentPrototype); + } + } + + loadComponentInfo(componentPrototype) { + if (!this.props.getComponentInfo) return; + this.setState({ isLoading: true }); + this.props + .getComponentInfo(componentPrototype) + .then((componentInfo) => { + this.setState({ + componentInfo, + isLoading: false, + }); + }) + .catch((e) => { + this.setState({ errorMsg: e.message }); + console.error(e); + if (VisualEngine) { + VisualEngine.ui.Popup.error({ + content: e.message, + duration: 2000, + }); + } + }); + } + + render() { + const { componentPrototype, subTitle, customImage } = this.props; + const { componentInfo } = this.state; + + const loadingContent = this.state.errorMsg ? ( +
+
{this.state.errorMsg}
+
+ ) : ( +
+ +
+ {$i18n.get({ id: "trunkPaneLoading", dm: "加载中..." })} +
+
+ ); + + const { + title, + version, + image = "https://img.alicdn.com/tfs/TB1XHG6ehrI8KJjy0FpXXb5hVXa-740-608.png", + desc, + detailUrl, + actions, + } = componentInfo; + + let layerContent = null; + const cardTitle = subTitle && !subTitle.includes(title) ? `${title}(${subTitle})` : title; + + if (this.state.isLoading) { + layerContent = ( +
+ {loadingContent} +
+ ); + } else { + layerContent = ( +
+
+
+ {cardTitle} + {version} +
+
+ {cardTitle} +
+
+
+
+

{desc}

+
+
+ {detailUrl ? ( + + {$i18n.get({ + id: "trunkPaneDetailedDocumentation", + dm: "详细文档", + })} + + ) : null} +
+ {actions + ? actions.map((action, idx) => { + return ( +
+
+
+
+ ); + } + + return ( + + {layerContent} + + ); + } +} diff --git a/packages/plugin-components-pane/src/components/card/index.less b/packages/plugin-components-pane/src/components/card/index.less new file mode 100644 index 000000000..00d7960eb --- /dev/null +++ b/packages/plugin-components-pane/src/components/card/index.less @@ -0,0 +1,130 @@ +@import "~@ali/ve-less-variables/index.less"; + +.ve-card-wrapper { + width: 320px; + max-height: 360px; + overflow: auto; + justify-content: center; + + &.ve-card-wrapper-loading { + display: flex; + justify-content: center; + text-align: center; + overflow: hidden; + } + + .ve-card-top { + height: 186px; + background: rgba(31, 56, 88, 0.1); + padding: 16px; + + .ve-card-title { + display: flex; + align-items: center; + padding-bottom: 12px; + position: relative; + height: 31px; + color: rgba(0, 0, 0, 0.8); + + .title { + line-height: 22px; + font-size: 16px; + } + + .version { + margin-left: 8px; + font-size: 12px; + line-height: 22px; + color: rgba(31, 56, 88, 0.4); + } + } + + .ve-card-image-wrapper { + background-color: #fff; + .ve-card-image { + max-width: 100%; + height: 120px; + margin: 0 auto; + display: block; + padding: 10px; + } + } + } + + .ve-card-bottom { + padding: 8px 16px 16px 16px; + .ve-card-description { + user-select: text; + text-align: left; + line-height: 18px; + font-size: 12px; + color: rgba(0, 0, 0, 0.6); + letter-spacing: 0; + line-height: 18px; + width: 280px; + + p { + margin-bottom: 8px; + } + + p:last-child { + margin-bottom: 0; + } + } + + .ve-operation-container { + margin-top: 12px; + position: relative; + .actions { + margin-top: 16px; + } + } + } + + .ve-operation-item { + color: @brand-color-1; + cursor: pointer; + margin-right: 8px; + } + + .ve-operation-delete { + position: absolute; + top: 0; + right: 0; + } + + .ve-card-more-operation { + display: flex; + justify-content: space-between; + > a { + cursor: pointer; + } + } + + .ve-loading-icon { + animation: spining 0.9s infinite linear; + } + .ve-loading-content { + padding: 10px; + text-align: center; + } + + .ve-card-action { + padding: 0 8px !important; + margin-right: 8px; + } +} + +.ve-component-list { + .vu-layer { + border-radius: 4px !important; + .vu-layer-content { + padding-top: 0 !important; + } + } + + .vu-layer .vu-layer-close { + right: 9px !important; + top: 9px !important; + } +} diff --git a/packages/plugin-components-pane/src/components/component-list/index.js b/packages/plugin-components-pane/src/components/component-list/index.js new file mode 100644 index 000000000..b14eb7f78 --- /dev/null +++ b/packages/plugin-components-pane/src/components/component-list/index.js @@ -0,0 +1,279 @@ +import Base, { AdditiveType } from "../base/index.js"; +import Snippet from "../snippet"; +import "./index.less"; + +// 滚动事件触发灵敏度 +const OFFSET_ACCURCY = 25; + +const categoryMap = { + General: "常用", + Navigation: "导航", + DataEntry: "输入", + DataDisplay: "展示", + Feedback: "反馈", + Util: "工具", + Chart: "图表", + Others: "其他", +}; + +export default class ComponentList extends Base { + static displayName = "ComponentList"; + + descRefList = new Map(); + navRefList = new Map(); + descHeightList = new Map(); + snippetMap = new Map(); + currentScrollHeight = 0; + scrollFlag = true; + scroll; + scrollTimer; + state = { + selected: "", + searchText: "", + currentCard: null, + currentCardImage: '' + }; + + componentDidMount() { + setTimeout(() => { + this.calDescHeightList(); + + // mock 滚动结束事件 + if (this.scroll) { + this.scroll.addEventListener("scroll", this.handleScrollEnd); + } + }, 20); + const bundle = this.normalizeBundle(); + if (!bundle) { + return; + } + const cats = Object.keys(bundle); + if (cats.length > 0) { + const k = cats[0]; + const comps = bundle[k]; + if (!comps || !Array.isArray(comps) || !comps.length) { + return; + } + this.setState({ selected: comps[0].id }); + } + } + + componentWillUnmount() { + if (this.scroll) { + this.scroll.removeEventListener("scroll", this.handleScrollEnd); + } + } + + handleScrollEnd = () => { + clearTimeout(this.scrollTimer); + this.scrollTimer = setTimeout(() => { + // 滚动结束时归位 + this.scrollFlag = true; + }, 100); + }; + + toggleComponent(id) { + const element = this.descRefList.get(id); + // 切换组件时滚动事件回调不可用 + this.scrollFlag = false; + element.scrollIntoView(); + this.setState({ selected: id }); + } + + handleScroll(e) { + // 清空卡片 + if (this.state.currentCard) { + this.setState({ currentCard: null }); + } + clearTimeout(this.timer); + this.timer = null; + + if (!this.scrollFlag) { + return; + } + const element = e.target; + const { scrollTop } = element; + + // 延迟处理 + if (Math.abs(scrollTop - this.currentScrollHeight) < OFFSET_ACCURCY) { + return; + } + this.currentScrollHeight = scrollTop; + + // 处理导航块滚动高亮效果 + const heightList = [...this.descHeightList.entries()]; + let selected; + // 当在顶部 + if (scrollTop >= 0 && scrollTop < heightList[0][1]) { + selected = heightList[0][0]; + } else if (scrollTop >= heightList[heightList.length - 1][1]) { + // 底部 + selected = heightList[heightList.length - 1][0]; + } else { + // 当在中部 + for (let i = 0; i < heightList.length - 2; i++) { + const height1 = heightList[i][1]; + const height2 = heightList[i + 1][1]; + if (scrollTop > height1 && scrollTop < height2) { + selected = heightList[i + 1][0]; + } + } + } + this.fixSideBarView(selected); + this.setState({ selected }); + } + + // 导航高亮块超出视口时滚动 + fixSideBarView(selected) { + const nav = this.navRefList.get(selected); + if (nav) { + nav.scrollIntoView({ block: "center" }); + } + } + + calDescHeightList() { + let height = 0; + this.descRefList.forEach((ele, key) => { + if (!ele) { + return; + } + height += ele.clientHeight; + this.descHeightList.set(key, height); + }); + } + + renderNavigator(bundle) { + return ( +
+ {Object.keys(bundle).map((c) => { + const catTitle = categoryMap[c] || c; + const catData = bundle[c]; + return this.renderNavigatorGroup(catTitle, catData); + })} +
+ ); + } + + renderNavigatorGroup(title, metaData) { + const { selected } = this.state; + if (!metaData) { + return null; + } + return ( +
+
+
{title}
+
+ {metaData.map((comp) => ( +
{ + this.navRefList.set(comp.id, item); + }} + onClick={() => this.toggleComponent(comp.id)} + > + {comp.title} +
+ ))} +
+ ); + } + + renderComponentDescriptionList(bundle) { + const { renderCustomSnippet = "", enableCard = true } = this.props; + return ( +
+ {Object.keys(bundle).map((cat) => { + const catData = bundle[cat]; + if (!catData) { + return null; + } + return catData.map((comp) => ( +
{ + this.descRefList.set(comp.id, item); + }} + > +
+ {comp.title ? ( +
+ {comp.title} +
+ ) : null} +
+
+
+
+ {comp.snippets && + comp.snippets.map((snippet, idx) => ( +
{ + this.snippetMap.set(`${comp.id}-${idx}`, i); + }} + onMouseEnter={() => { + if (!enableCard) { + return; + } + this.timer = setTimeout(() => { + this.setState({ + currentCard: comp, + target: this.snippetMap.get(`${comp.id}-${idx}`), + currentCardImage: snippet.thumbnail + }); + this.timer = null; + }, 1000); + }} + onMouseLeave={() => { + if (!enableCard) { + return; + } + clearTimeout(this.timer); + this.timer = null; + setTimeout(() => { + if (this.isMouseEnterCard) { + return; + } + this.setState({currentCard: null}); + }, 200); + }} + > + +
+ ))} +
+
+ )); + })} +
+ ); + } + + getBundle(bundle) { + return [ +
+ {this.renderNavigator(bundle)} +
, +
this.handleScroll(e)} + ref={(scroll) => (this.scroll = scroll)} + > + {this.renderComponentDescriptionList(bundle)} +
, + ]; + } +} diff --git a/packages/plugin-components-pane/src/components/component-list/index.less b/packages/plugin-components-pane/src/components/component-list/index.less new file mode 100644 index 000000000..62f60096f --- /dev/null +++ b/packages/plugin-components-pane/src/components/component-list/index.less @@ -0,0 +1,117 @@ +@import "~@ali/ve-less-variables/index.less"; + +@text-color-active: #0079f2; + +.ve-component-list { + .ve-component-list-body { + .ve-component-list-sidebar { + flex: 0 0 110px; + width: 110px; + // height: 100%; + padding-top: 11px; + border-right: 1px solid var(--color-line-normal, rgba(31, 56, 88, 0.1)); + overflow: auto; + .ve-component-list-navigator { + .navigator-group { + margin-bottom: 8px; + & > div { + display: flex; + align-items: center; + width: 100%; + } + .navigator-group-head { + padding: 0px 12px; + color: @dark-alpha-2; + .navigator-group-title { + padding: 4px 0; + width: 100%; + line-height: 16px; + font-size: @fontSize-5; + font-weight: 700; + border-bottom: 1px solid + var(--color-line-normal, rgba(31, 56, 88, 0.1)); + } + } + .navigator-group-item { + height: 32px; + padding: 8px 12px; + color: #666666; + cursor: pointer; + .navigator-group-item-title { + font-size: @fontSize-5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: left; + } + &:hover { + color: @text-color-active; + } + &.active { + background: rgba(0, 121, 242, 0.1); + color: @text-color-active; + border-right: 2px solid @text-color-active; + } + } + } + } + } + .ve-component-list-content { + flex: 1; + width: 250px; + // height: 100%; + overflow-y: auto; + overflow-x: hidden; + .component-description-list { + padding: 10px 4px; + .component-description-item { + padding-bottom: 24px; + .component-description-item-header { + display: flex; + justify-content: flex-start; + align-items: center; + .component-description-item-title { + font-family: @font-family; + font-size: @fontSize-5; + font-weight: 700; + color: @text-color; + line-height: 20px; + padding-left: 8px; + } + .component-description-item-icon-group { + display: flex; + justify-content: flex-start; + align-items: center; + .component-description-item-icon { + margin-left: 4px; + } + icon { + position: relative; + font-size: 14px; + cursor: pointer; + color: rgba(31, 56, 88, 0.4); + transition: color @transition-duration @transition-ease; + &:hover { + color: rgba(31, 56, 88, 0.6); + } + a { + position: absolute; + left: 0; + top: 0; + display: inline-block; + width: 14px; + height: 14px; + } + } + } + } + .component-description-item-snippets { + display: flex; + justify-content: flex-start; + flex-flow: row wrap; + } + } + } + } + } +} diff --git a/packages/plugin-components-pane/src/components/snippet/index.js b/packages/plugin-components-pane/src/components/snippet/index.js new file mode 100644 index 000000000..fed12c8e7 --- /dev/null +++ b/packages/plugin-components-pane/src/components/snippet/index.js @@ -0,0 +1,46 @@ +import { Fragment } from "react"; +import { Icon } from "@alifd/next"; +import { AdditiveType } from "../base"; +import "./index.less"; + +const Snippet = (props) => { + const { + snippet, + renderCustomSnippet = "", + size = "small", + actionsInLT, + actionsInRT, + } = props; + const { + thumbnail = "https://img.alicdn.com/tfs/TB1XHG6ehrI8KJjy0FpXXb5hVXa-740-608.png", + description, + title = "未知" + } = snippet; + const snippetClassName = `component-description-item-snippet ${AdditiveType.All} ${size}`; + return ( +
+ {typeof renderCustomSnippet === "function" ? ( + renderCustomSnippet(snippet) + ) : ( + +
+ {typeof thumbnail === "string" && thumbnail.startsWith("http") ? ( + thumbnail + ) : ( + + )} +
+
{description || title}
+
+ {actionsInLT ? actionsInLT : null} +
+
+ {actionsInRT ? actionsInRT : null} +
+
+ )} +
+ ); +}; + +export default Snippet; diff --git a/packages/plugin-components-pane/src/components/snippet/index.less b/packages/plugin-components-pane/src/components/snippet/index.less new file mode 100644 index 000000000..6c3809bfa --- /dev/null +++ b/packages/plugin-components-pane/src/components/snippet/index.less @@ -0,0 +1,120 @@ +@import "~@ali/ve-less-variables/index.less"; + +@text-color-active: #0079f2; + +.component-description-item-snippet { + position: relative; + &.small { + width: 64px; + height: 66px; + .snippet-thumbnail { + height: 40px; + img, + .icon { + max-width: 56px; + max-height: 32px; + } + } + } + &.large { + width: 132px; + height: 126px; + .snippet-thumbnail { + height: 100px; + img, + .icon { + max-width: 116px; + max-height: 83px; + } + } + } + margin: 12px 8px 0 8px; + cursor: grab; + .snippet-title { + width: 100%; + padding: 0 4px; + font-size: @fontSize-5; + color: #666666; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + .snippet-thumbnail { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + margin-bottom: 9px; + border-radius: @global-border-radius; + border: 1px dashed #cdd2d9; + transition: border @transition-duration @transition-ease; + img, + .icon { + width: auto; + height: auto; + } + svg { + font-size: 28px; + } + svg * { + fill: rgba(31, 56, 88, 0.6) !important; + } + } + .component-description-item-icon { + &.tag { + display: flex; + justify-content: center; + align-items: center; + background: rgba(249, 189, 15, 0.1); + color: #ff6f00; + font-size: 12px; + border-radius: 3px; + width: 48px; + height: 24px; + } + } + .engine-additive-helper { + &.left-top { + position: absolute; + left: 0; + top: 0; + } + &.right-top { + display: none; + } + icon { + position: relative; + font-size: 14px; + cursor: pointer; + color: rgba(31, 56, 88, 0.4); + transition: color @transition-duration @transition-ease; + &:hover { + color: rgba(31, 56, 88, 0.6); + } + a { + position: absolute; + left: 0; + top: 0; + display: inline-block; + width: 14px; + height: 14px; + } + } + } + &:hover { + .snippet-thumbnail { + border: 2px solid @text-color-active; + transition: border @transition-duration @transition-ease; + } + .right-top { + display: block; + position: absolute; + right: 0; + top: 0; + height: 24px; + display: flex; + align-items: center; + } + } +} diff --git a/packages/plugin-components-pane/src/i18n/index.js b/packages/plugin-components-pane/src/i18n/index.js new file mode 100644 index 000000000..eae0b99e3 --- /dev/null +++ b/packages/plugin-components-pane/src/i18n/index.js @@ -0,0 +1,45 @@ +const { provideIntl, destroyIntl } = require('@ali/intl-universal'); +const strings = require('./strings'); + +let intl; +const MEDUSA_APP_NAME = 'legao-designer'; +const PSEUDO_LANGUAGE_TAG = 'pd-KV'; +const CURRENT_LANGUAGE = (window.locale || '').replace(/_/, '-') || 'zh-CN'; + +function update(language) { + destroyIntl(); + intl = provideIntl({ + locale: language, + messagesAIO: strings, + }); +} + +function get(id, variable) { + if (!intl) update(); + let string = ''; + let key = ''; + if (typeof id === 'string') { + key = id; + string = intl.formatMessage({ id }, variable); + } + if (typeof id === 'object' && id.dm) { + id.defaultMessage = id.dm; + } + key = id.id; + string = intl.formatMessage(id, variable); + if (CURRENT_LANGUAGE === PSEUDO_LANGUAGE_TAG) { + return `##@@@${key}##${MEDUSA_APP_NAME}@@@##${string}`; + } + return string; +} + +if (PSEUDO_LANGUAGE_TAG === CURRENT_LANGUAGE) { + update('en-US'); +} else { + update(CURRENT_LANGUAGE); +} + +module.exports = { + get, + update, +}; diff --git a/packages/plugin-components-pane/src/i18n/strings/en-US.json b/packages/plugin-components-pane/src/i18n/strings/en-US.json new file mode 100644 index 000000000..f6873f90d --- /dev/null +++ b/packages/plugin-components-pane/src/i18n/strings/en-US.json @@ -0,0 +1,27 @@ +{ + "trunkPaneTheComponentPrototypeView": "The component prototype view does not exist and may be caused by the following reasons:", + "trunkPaneDetailedComponentInformationIs": "Detailed component information is already printed in the Console", + "trunkPaneIsTheComponentRemoved": "Is the component removed because the component cannot find the prototype view?", + "trunkPaneLoading": "Loading...", + "trunkPaneFoundComponentNameRComponentName": "Found component name: {rComponentName} has a problem", + "trunkPaneDetailedDocumentation": "Detailed documentation", + "trunkPaneComponentComponentInfoComponentNameDeletedSuccessfully": "Component {componentInfoComponentName} deleted successfully", + "trunkPaneUpdateComponent": "Update component", + "trunkPaneComponentLibrary": "Component library", + "trunkPaneSearchComponent": "Search component", + "trunkPaneCustomize": "customize", + "trunkPaneTheComponentInQuestion": "The component in question:", + "trunkPaneComponentMarket": "Component market", + "trunkPaneUncategorized": "uncategorized", + "trunkPaneNoSearchResultsYet": "No search results yet", + "trunkPaneAll": "All", + "trunkPaneClickToTheLEGO": "Click ? to the Component Center", + "trunkPaneClickToTroubleshootThe": "Click to troubleshoot the issue", + "trunkPaneComponent": "Component", + "trunkPaneComponentCenter": "Component center", + "trunkPaneClose": "╳ Close", + "trunkPaneDetail": "Detail", + "trunkPaneEdit": "Edit", + "trunkPaneDev": "Dev", + "trunkPaneHelp": "Help" +} diff --git a/packages/plugin-components-pane/src/i18n/strings/index.js b/packages/plugin-components-pane/src/i18n/strings/index.js new file mode 100644 index 000000000..9b61c93e1 --- /dev/null +++ b/packages/plugin-components-pane/src/i18n/strings/index.js @@ -0,0 +1,4 @@ +module.exports = { + 'en-US': require('./en-US.json'), + 'zh-CN': require('./zh-CN.json'), +}; diff --git a/packages/plugin-components-pane/src/i18n/strings/zh-CN.json b/packages/plugin-components-pane/src/i18n/strings/zh-CN.json new file mode 100644 index 000000000..82f52faf1 --- /dev/null +++ b/packages/plugin-components-pane/src/i18n/strings/zh-CN.json @@ -0,0 +1,26 @@ +{ + "trunkPaneAll": "全部", + "trunkPaneFoundComponentNameRComponentName": "发现组件名为:{rComponentName}存在问题", + "trunkPaneNoResultsYet": "暂无结果", + "trunkPaneUpdateComponent": "更新组件", + "trunkPaneClickToTroubleshootThe": "点击排查问题", + "trunkPaneClickToTheLEGO": "点击 ? 前往组件中心", + "trunkPaneComponentLibrary": "组件库", + "trunkPaneComponentCenter": "组件中心", + "trunkPaneDetailedComponentInformationIs": "详细的组件信息已经打印在 Console 中", + "trunkPaneTheComponentPrototypeView": "组件原型视图不存在,可能由以下原因导致:", + "trunkPaneLoading": "加载中...", + "trunkPaneTheComponentInQuestion": "存在问题的组件:", + "trunkPaneIsTheComponentRemoved": "由于该组件找不到原型视图,是否移除组件?", + "trunkPaneDetailedDocumentation": "详细文档", + "trunkPaneUncategorized": "未分类", + "trunkPaneComponentMarket": "组件市场", + "trunkPaneCustomize": "自定义", + "trunkPaneSearchComponent": "搜索组件", + "trunkPaneComponent": "组件", + "trunkPaneClose": "╳ 关闭", + "trunkPaneDetail": "详情", + "trunkPaneEdit": "编辑", + "detrunkPaneDev": "开发版", + "trunkPaneHelp": "帮助" +} diff --git a/packages/plugin-components-pane/src/index.tsx b/packages/plugin-components-pane/src/index.tsx index d351734f0..b3f5ee578 100644 --- a/packages/plugin-components-pane/src/index.tsx +++ b/packages/plugin-components-pane/src/index.tsx @@ -1,5 +1,7 @@ import { Component, ReactNode } from 'react'; -import ComponentList, { AdditiveType } from "@ali/ve-component-list"; +import { Tab } from '@alifd/next'; +import ComponentList from "./components/component-list"; +import { AdditiveType } from "./components/base" import { PluginProps } from '@ali/lowcode-types'; import { Designer } from '@ali/lowcode-designer'; @@ -7,6 +9,7 @@ import './index.scss'; export interface IState { metaData: object[]; + bizComponents: object[]; } export default class ComponentListPlugin extends Component { @@ -18,6 +21,7 @@ export default class ComponentListPlugin extends Component super(props); this.state = { metaData: [], + bizComponents: [], }; } @@ -61,9 +65,11 @@ export default class ComponentListPlugin extends Component const { editor } = this.props; const assets = editor.get('assets') || {}; const metaData = this.transformMetaData(assets.componentList); + const bizComponents = this.transformMetaData(assets.bizComponentList); this.setState({ metaData, + bizComponents, }); }; @@ -126,15 +132,27 @@ export default class ComponentListPlugin extends Component } render(): ReactNode { - const { metaData } = this.state; + const { metaData, bizComponents } = this.state; return (
- this.registerAdditive(shell)} - enableSearch - /> + + + this.registerAdditive(shell)} + enableSearch + /> + + + this.registerAdditive(shell)} + enableSearch + /> + +
); } diff --git a/packages/plugin-components-pane/src/utils.js b/packages/plugin-components-pane/src/utils.js new file mode 100644 index 000000000..fc46ee975 --- /dev/null +++ b/packages/plugin-components-pane/src/utils.js @@ -0,0 +1,99 @@ +export const builtinSearchMap = { + "容器": "容器、rongqi、rq、分栏、ColumnsLayout、Columns、layout、grid", + "分栏": "分栏、ColumnsLayout、Columns、layout、grid、容器、rongqi、rq、fl、fenlan", + "卡片": "卡片、card、kapian、kp", + "选项卡": "选项卡、tab、tabs、页签、xuanxiangka、xxk、yeqian、yq", + "按钮": "按钮、button、anniu、an", + "图标": "图标、icon、tubiao、tb", + "标题": "标题、title、biaoti、bt", + "图片": "图片、image、pic、picture、tupian、tp", + "Dialog": "对话框、Dialog、弹框、弹出框、duihuakuang、dhk", + "Drawer": "抽屉、Drawer、chouti、ct、couti", + "文本": "文本、文字、text、wenzi、wz、wenben", + "链接": "链接、link、lianjie、lj", + "链接块": "链接块、链接、link、lianjie、lj、ljk", + "表单容器": "表单容器、表单、form、biaodan、bd", + "输入框": "输入框、文本框、密码框、input、shurukuang、srk、wenbenkuang、wbk", + "数字输入框": "数字输入框、数字、输入框、Number、NumberPicker、shuzi、sz、srk、shurukuang", + "单选": "单选、radio button、radio、danxuan、dx", + "多选": "复选、复选框、多选、Checkbox、check、fuxuan、fx、dx、duoxuan", + "下拉选择": "下拉选择、Select、选择器、下拉、xiala、xl、xialaxuanze、xlxz", + "开关": "开关、switch、kaiguan、kg", + "日期": "日期选择、date、DatePicker、riqi、riqixuanz、rq、rqxz", + "日期区间": "日期区间、Cascade、date、riqiqujian、rq", + "时间选择框": "时间选择、time、TimePicker、shijian、sj、shijianxuanze、sjxz、xuanzekuang", + "上传图片": "上传图片、upload、上传、shangchuan、sc、tupian", + "上传附件": "上传附件、upload、上传、shangchuan、sc、fujian", + "树形选择": "树形选择、树型选择、树选择、tree、TreeSelect、shu、sxz、shuxingxuanze", + "级联选择": "级联选择、Cascade、Cascadeselect、级联、jilian、jl、jilianxuanze、jlxz", + "地区选择": "地区选择、city、地址、address、地区、diqu、dq、diquxuanze、dqxz", + "国家选择": "国家选择、country、国家、guojia、gj、guojiaxuanze、gjxz", + "评分": "评分、Rate、Rating、星、pingfen、pf", + "明细": "明细、table、表格、表单、form、mingxi、mx", + "穿梭框": "穿梭框、transfer、chuansuokuang、csk", + "人员搜索框": "人员搜索框、employee、人员选择、选人、xuanren、xr、renyuansousuo、ryss", + "筛选": "筛选、pickable、shaixuan、sx", + "金额输入框": "金额输入框、输入框、shurukuang、srk、money、金额、jine、je", + "金额区间": "金额区间、money、金额、jine、je", + "查询": "查询、filter、chaxun、cx", + "表格": "表格、table、biaoge、bg", + "数据文本": "数据文本、Number Info、数据、shuju、sj、shujuwenben、sjwb", + "数据趋势": "数据趋势、Number Trend、数据、shuju、sj、shujuqushi、sjqs", + "勾选框": "勾选框、复选框、check box、gouxuankuang、gxk、fuxuankuang、fxk", + "图片浏览": "图片浏览、图片预览、image、pic、picture、图片、预览、tupianyulan、tupianliulan、tupian、tp、yulan、yl", + "搜索": "搜索、搜索框、查询框、查询、search、sousuo、ss", + "树形控件": "树形控件、树组件、tree、shuzujian、shuxingkongjian、shu、szj、sxkj", + "富文本框": "富文本框、RichText、fuwenben、fwb", + "步骤": "步骤、步骤条、step、steps、buzhoutiao、buzhou、bzt、bz", + "时间轴": "时间轴、时间线、timeline、shijianzhou、shijianxian、sjz、sjx", + "菜单": "菜单、menu、caidan、cd", + "气泡提示": "气泡提示、tip、tips、balloon、气泡、qipao、qp、qipaotishi、qpts", + "面包屑": "面包屑、breadcrumb、crumb、mianbaoxie、mbx", + "日历": "日历、calendar、rili、rl", + "折叠面板": "折叠面板、collapse、折叠、zhedie、zd、zhediemianban、zdmb", + "下拉菜单": "下拉菜单、dropdown、下拉、xiala、xl、xialacaidan、xlcd、菜单、caidan、cd", + "信息提示": "信息提示、message、alert、信息、提示、警示、xinxitishi、xxts、xinxi、xx、tishi、ts、消息、xiaoxi", + "进度指示器": "进度指示器、进度条、progress、jindutiao、jdt、进度、jindu、jd", + "翻页器": "翻页器、分页器、pagination、fanyeqi、fyq、分页、fenye、fy", + "轮播图": "轮播图、图片轮播、slider、轮播、lunbo、lb、lunbotu、lbt", + "底部通栏": "底部通栏、tool bar、通栏、dibutonglan、dbtl、浮动工具条、浮动、工具条、工具、fudong、gongju、toolbar、tool bar、fd、gj", + "HTML": "html", + "JSX": "jsx", + "浮动导航": "浮动导航、nav、floatNav、fudongdaohang、fddh", + "Iframe": "Iframe", + "Markdown": "Markdown", + "区段选择器": "区段选择、滑块选择、区段、滑块、选择、Range、quduan、huakuai、xuanze、qdxz、hkxz、xz" +}; + +/** + * + * @param {string} title 组件名 + * @param {string} query 搜索词 + * @param {object} map 映射关系 + */ +export function searchComponent(title, query, map = {}) { + if (!title || !query || !map || !map[title]) { + return false; + } + const keys = (map[title] || '').split('、'); + return !!keys.find(key => { + if (!key) { + return false; + } + return key.indexOf(query) > -1 + }); +} + +export function debounce(func, delay) { + let timer + return function(...args) { + if (timer) { + return + } + timer = setTimeout(() => { + func.apply(this, args) + clearTimeout(timer) + timer = null + }, delay) + } +}