From 524858150554c77988c4867a6bc3d4dd8e7fda34 Mon Sep 17 00:00:00 2001 From: kun Date: Tue, 28 Sep 2021 19:17:22 +0800 Subject: [PATCH 1/8] feat: add icon display --- app/assets/components/ColorPicker/index.tsx | 27 ++- .../Expand/ExpandForm/CustomSet.tsx | 55 ++++++ .../components/Expand/ExpandForm/index.less | 2 +- .../components/Expand/ExpandForm/index.tsx | 122 +++++++------ app/assets/components/IconPicker/iconCfg.ts | 124 ++++++++++++++ app/assets/components/IconPicker/index.less | 24 +++ app/assets/components/IconPicker/index.tsx | 54 ++++++ app/assets/components/NebulaD3/index.less | 10 +- app/assets/components/NebulaD3/index.tsx | 114 ++++++++++--- app/assets/components/VertexDisplay/index.tsx | 160 ++++++++++++++++++ app/assets/config/explore.ts | 2 +- app/assets/config/locale/en-US.json | 16 +- app/assets/config/locale/zh-CN.json | 16 +- .../Explore/NebulaGraph/Menu/index.less | 14 ++ .../Explore/NebulaGraph/Menu/index.tsx | 4 +- .../NebulaGraph/Panel/ColorPicker/index.less | 18 -- .../NebulaGraph/Panel/ColorPicker/index.tsx | 156 ----------------- .../NebulaGraph/Panel/VertexSet/index.less | 122 +++++++++++++ .../NebulaGraph/Panel/VertexSet/index.tsx | 122 +++++++++++++ .../Explore/NebulaGraph/Panel/index.tsx | 4 +- app/assets/modules/Import/Tasks/Upload.tsx | 68 ++++++-- app/assets/static/fonts/iconfont.css | 126 +++++++++++++- app/assets/static/fonts/iconfont.ttf | Bin 28172 -> 35776 bytes app/assets/static/fonts/iconfont.woff | Bin 17988 -> 22760 bytes app/assets/static/fonts/iconfont.woff2 | Bin 15388 -> 19260 bytes app/assets/store/models/d3Graph.ts | 2 + app/assets/store/models/explore.ts | 34 ++-- app/assets/utils/interface.ts | 1 + 28 files changed, 1077 insertions(+), 320 deletions(-) create mode 100644 app/assets/components/Expand/ExpandForm/CustomSet.tsx create mode 100644 app/assets/components/IconPicker/iconCfg.ts create mode 100644 app/assets/components/IconPicker/index.less create mode 100644 app/assets/components/IconPicker/index.tsx create mode 100644 app/assets/components/VertexDisplay/index.tsx delete mode 100644 app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.less delete mode 100644 app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.tsx create mode 100644 app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.less create mode 100644 app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx diff --git a/app/assets/components/ColorPicker/index.tsx b/app/assets/components/ColorPicker/index.tsx index 2012a419..2103fe4a 100644 --- a/app/assets/components/ColorPicker/index.tsx +++ b/app/assets/components/ColorPicker/index.tsx @@ -1,4 +1,3 @@ -import { Popover } from 'antd'; import React from 'react'; import { TwitterPicker } from 'react-color'; @@ -38,23 +37,17 @@ class ColorPicker extends React.Component { render() { return ( - - } - trigger="click" - > +
+ {this.props.children} - +
); } } diff --git a/app/assets/components/Expand/ExpandForm/CustomSet.tsx b/app/assets/components/Expand/ExpandForm/CustomSet.tsx new file mode 100644 index 00000000..115cf83f --- /dev/null +++ b/app/assets/components/Expand/ExpandForm/CustomSet.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import MenuButton from '#assets/components/Button'; +import VertexDisplay from '#assets/components/VertexDisplay'; + +interface IProps { + showTitle?: boolean; + showIcon?: boolean; + customColor?: string; + customIcon?: string; + onColorChange?: (color: string) => void; + onIconChange?: (icon: string) => void; +} + +class CustomSet extends React.PureComponent { + handleChangeColorComplete = color => { + const { hex: _color } = color; + this.props.onColorChange?.(_color); + }; + + handleChangeIconComplete = icon => { + this.props.onIconChange?.(icon); + }; + + render() { + const { + showTitle, + showIcon, + customIcon, + customColor, + } = this.props; + return ( + <> + + } + trackCategory="explore" + trackAction="color_picker" + trackLabel="from_panel" + /> + + ) + } +} + +export default CustomSet; \ No newline at end of file diff --git a/app/assets/components/Expand/ExpandForm/index.less b/app/assets/components/Expand/ExpandForm/index.less index 56acbc81..2ffdf75e 100644 --- a/app/assets/components/Expand/ExpandForm/index.less +++ b/app/assets/components/Expand/ExpandForm/index.less @@ -128,7 +128,7 @@ .btn-color { position: absolute; right: 0; - bottom: 3px; + bottom: -12px; } } } diff --git a/app/assets/components/Expand/ExpandForm/index.tsx b/app/assets/components/Expand/ExpandForm/index.tsx index 46ead601..0046c35a 100644 --- a/app/assets/components/Expand/ExpandForm/index.tsx +++ b/app/assets/components/Expand/ExpandForm/index.tsx @@ -17,7 +17,7 @@ import { Instruction } from '#assets/components'; import GQLModal from '#assets/components/GQLModal'; import IconFont from '#assets/components/Icon'; import { DEFAULT_COLOR_PICKER } from '#assets/config/explore'; -import ColorPickerBtn from '#assets/modules/Explore/NebulaGraph/Panel/ColorPicker'; +import CustomSet from './CustomSet'; import { IDispatch, IRootState } from '#assets/store'; import { RELATION_OPERATORS } from '#assets/utils/constant'; import { getExploreMatchGQL } from '#assets/utils/gql'; @@ -61,6 +61,7 @@ interface IState { filters: IFilter[]; visible: boolean; customColor: string; + customIcon: string; } class Expand extends React.Component { @@ -71,6 +72,7 @@ class Expand extends React.Component { filters: [], visible: false, customColor: DEFAULT_COLOR_PICKER, + customIcon: '', }; this.gqlRef = React.createRef(); } @@ -78,11 +80,14 @@ class Expand extends React.Component { componentDidMount() { this.props.asyncGetEdgesAndFields(); const { - exploreRules: { filters }, + exploreRules: { filters, customIcon }, } = this.props; if (filters) { this.setState({ filters }); } + if (customIcon) { + this.setState({ customIcon }); + } } renderFilters = () => { @@ -138,47 +143,50 @@ class Expand extends React.Component { handleExpand = () => { const { selectVertexes, edgesFields } = this.props; const { getFieldsValue } = this.props.form; - const { filters, customColor } = this.state; + const { filters, customColor, customIcon } = this.state; this.props.form.validateFields(async err => { - if (!err) { - const { - edgeTypes, - edgeDirection, - stepsType, - step, - minStep, - maxStep, - vertexColor, - quantityLimit, - } = getFieldsValue(); - (this.props.asyncGetExpand({ - filters, - selectVertexes, - edgeTypes, - edgesFields, - edgeDirection, - vertexColor, - quantityLimit, - stepsType, - step, - minStep, - maxStep, - customColor, - }) as any).then( - async () => { - message.success(intl.get('common.success')); - trackEvent('explore', 'expand', 'ajax success'); - }, - (e: any) => { - trackEvent('explore', 'expand', 'ajax fail'); - if (e.message) { - message.error(e.message); - } else { - message.info(intl.get('common.noData')); - } - }, - ); + if (err) { + console.error(err); + return; } + const { + edgeTypes, + edgeDirection, + stepsType, + step, + minStep, + maxStep, + vertexSets, + quantityLimit, + } = getFieldsValue(); + (this.props.asyncGetExpand({ + filters, + selectVertexes, + edgeTypes, + edgesFields, + edgeDirection, + vertexSets, + quantityLimit, + stepsType, + step, + minStep, + maxStep, + customColor, + customIcon, + }) as any).then( + async () => { + message.success(intl.get('common.success')); + trackEvent('explore', 'expand', 'ajax success'); + }, + (e: any) => { + trackEvent('explore', 'expand', 'ajax fail'); + if (e.message) { + message.error(e.message); + } else { + message.info(intl.get('common.noData')); + } + }, + ); }); }; @@ -230,9 +238,18 @@ class Expand extends React.Component { ); }; + handleCustomIcon = icon => { + this.setState( + { + customIcon: icon.type, + }, + this.handleUpdateRules, + ); + }; + handleUpdateRules = () => { const { getFieldsValue } = this.props.form; - const { filters, customColor } = this.state; + const { filters, customColor, customIcon } = this.state; setTimeout(() => { const { edgeTypes, @@ -241,19 +258,20 @@ class Expand extends React.Component { step, minStep, maxStep, - vertexColor, + vertexSets, quantityLimit, } = getFieldsValue(); this.props.updateExploreRules({ edgeTypes, edgeDirection, - vertexColor, + vertexSets, quantityLimit, stepsType, step, minStep, maxStep, customColor, + customIcon, filters, }); }, 100); @@ -268,7 +286,7 @@ class Expand extends React.Component { close, } = this.props; const { getFieldDecorator, getFieldsValue } = this.props.form; - const { filters, customColor } = this.state; + const { filters, customColor, customIcon } = this.state; const { edgeTypes: selectEdgeTypes, edgeDirection, @@ -424,9 +442,9 @@ class Expand extends React.Component { )} - - {getFieldDecorator('vertexColor', { - initialValue: rules.vertexColor || 'groupByTag', + + {getFieldDecorator('vertexSets', { + initialValue: rules.vertexSets || 'groupByTag', rules: [ { required: true, @@ -440,10 +458,12 @@ class Expand extends React.Component { {intl.get('explore.customColor')} - , )} diff --git a/app/assets/components/IconPicker/iconCfg.ts b/app/assets/components/IconPicker/iconCfg.ts new file mode 100644 index 00000000..616ed325 --- /dev/null +++ b/app/assets/components/IconPicker/iconCfg.ts @@ -0,0 +1,124 @@ +const IconCfg = [ + { + type: 'iconimage-iconUnselect', + content: '', + }, + { + type: 'iconimage-icon1', + content: '\ue6fd', + }, + { + type: 'iconimage-icon2', + content: '\ue6ff', + }, + { + type: 'iconimage-icon3', + content: '\ue6fe', + }, + { + type: 'iconimage-icon4', + content: '\ue700', + }, + { + type: 'iconimage-icon5', + content: '\ue701', + }, + { + type: 'iconimage-icon6', + content: '\ue702', + }, + { + type: 'iconimage-icon7', + content: '\ue703', + }, + { + type: 'iconimage-icon8', + content: '\ue704', + }, + { + type: 'iconimage-icon9', + content: '\ue705', + }, + { + type: 'iconimage-icon10', + content: '\ue706', + }, + { + type: 'iconimage-icon11', + content: '\ue709', + }, + { + type: 'iconimage-icon12', + content: '\ue707', + }, + { + type: 'iconimage-icon13', + content: '\ue708', + }, + { + type: 'iconimage-icon14', + content: '\ue714', + }, + { + type: 'iconimage-icon15', + content: '\ue70e', + }, + { + type: 'iconimage-icon16', + content: '\ue70b', + }, + { + type: 'iconimage-icon17', + content: '\ue70a', + }, + { + type: 'iconimage-icon18', + content: '\ue712', + }, + { + type: 'iconimage-icon19', + content: '\ue710', + }, + { + type: 'iconimage-icon20', + content: '\ue70d', + }, + { + type: 'iconimage-icon21', + content: '\ue70c', + }, + { + type: 'iconimage-icon22', + content: '\ue70f', + }, + { + type: 'iconimage-icon23', + content: '\ue711', + }, + { + type: 'iconimage-icon24', + content: '\ue715', + }, + { + type: 'iconimage-icon25', + content: '\ue713', + }, + { + type: 'iconimage-icon26', + content: '\ue716', + }, + { + type: 'iconimage-icon27', + content: '\ue719', + }, + { + type: 'iconimage-icon28', + content: '\ue717', + }, + { + type: 'iconimage-icon29', + content: '\ue718', + }, +]; + +export default IconCfg; diff --git a/app/assets/components/IconPicker/index.less b/app/assets/components/IconPicker/index.less new file mode 100644 index 00000000..4022752b --- /dev/null +++ b/app/assets/components/IconPicker/index.less @@ -0,0 +1,24 @@ +.icon-picker { + .icon-box { + display: inline-block; + width: 40px; + height: 40px; + padding: 5px; + background-color: #f3f2f2; + cursor: pointer; + margin: 5px; + color: #000; + + .nebula-cloud-icon { + font-size: 30px; + display: inline-block; + margin-top: -6px; + } + } + + .slick-slide:nth-child(2) { + .icon-box:first-child { + color: #c00a0a; + } + } +} diff --git a/app/assets/components/IconPicker/index.tsx b/app/assets/components/IconPicker/index.tsx new file mode 100644 index 00000000..e4d1abe6 --- /dev/null +++ b/app/assets/components/IconPicker/index.tsx @@ -0,0 +1,54 @@ +import { Carousel, Popover } from 'antd'; +import { chunk } from 'lodash'; +import React from 'react'; +import Icon from '#assets/components/Icon'; +import IconCfg from './iconCfg'; +import './index.less'; + +interface IIcon { + type: string; + content: string +} + +interface IProps { + handleChangeIconComplete?: (icon: IIcon) => void; +} + +const iconGroup = chunk(IconCfg, 16); + +function IconItem({ icon, onClick }: { icon: T; onClick: (icon: T) => void; }) { + const { type, content } = icon; + const iconElement = onClick(icon)} />; + + return ( +
+ {!!content ? iconElement : {iconElement}} +
+ ); +} + +class IconPickerBtn extends React.PureComponent { + handleChangeIconComplete = (icon: IIcon) => { + this.props.handleChangeIconComplete?.(icon); + }; + + render() { + return ( +
+ + {iconGroup.map((group, index) => ( +
+ {group.map(icon => ( + + ))} +
+ ))} +
+
+ ); + } +} +export default IconPickerBtn; diff --git a/app/assets/components/NebulaD3/index.less b/app/assets/components/NebulaD3/index.less index 036d0438..dd590fc3 100644 --- a/app/assets/components/NebulaD3/index.less +++ b/app/assets/components/NebulaD3/index.less @@ -15,10 +15,12 @@ height: 100%; .node { - &.active { - stroke: rgba(0, 0, 0, 0.5); - stroke-width: 6; - r: 20; + .circle { + &.active { + stroke: rgba(0, 0, 0, 0.5); + stroke-width: 6; + r: 20; + } } } } diff --git a/app/assets/components/NebulaD3/index.tsx b/app/assets/components/NebulaD3/index.tsx index 49d3730f..91eede6e 100644 --- a/app/assets/components/NebulaD3/index.tsx +++ b/app/assets/components/NebulaD3/index.tsx @@ -2,6 +2,7 @@ import * as d3 from 'd3'; import * as React from 'react'; import { connect } from 'react-redux'; +import IconCfg from '#assets/components/IconPicker/iconCfg'; import Menu from '#assets/modules/Explore/NebulaGraph/Menu'; import { IRootState } from '#assets/store'; import { INode, IPath } from '#assets/utils/interface'; @@ -17,7 +18,6 @@ const mapState = (state: IRootState) => ({ isZoom: state.d3Graph.isZoom, scale: state.d3Graph.canvasScale, }); - interface IProps extends ReturnType { width: number; height: number; @@ -38,7 +38,8 @@ interface IProps extends ReturnType { } class NebulaD3 extends React.Component { - nodeRef: SVGCircleElement; + nodeRef: SVGGElement; + circleRef: SVGCircleElement; canvasBoardRef: SVGCircleElement; force: any; svg: any; @@ -46,6 +47,7 @@ class NebulaD3 extends React.Component { link: any; linksText: any; nodeText: any; + iconText: any; componentDidMount() { this.svg = d3.select('#output-graph'); @@ -95,7 +97,10 @@ class NebulaD3 extends React.Component { componentDidUpdate() { const { data, selectedNodes } = this.props; - this.handleUpdataNodes(data.vertexes, selectedNodes); + this.handleDeleteNodes(data.vertexes); + this.handleUpdateNodes(data.vertexes, selectedNodes); + this.handleUpdateIcons(data.vertexes); + this.force.on('tick', () => this.tick()); } handleNodeClick = (d: any) => { @@ -214,8 +219,8 @@ class NebulaD3 extends React.Component { d.target.y ); }); - this.node.attr('cx', d => d.x).attr('cy', d => d.y); + this.iconText?.attr('x', d => d.x).attr('y', d => d.y); d3.selectAll('.text') .attr('transform-origin', (d: any) => { @@ -254,19 +259,37 @@ class NebulaD3 extends React.Component { this.nodeRenderText(); }; - handleUpdataNodes(nodes: INode[], selectNodes: INode[]) { - const selectNodeNames = selectNodes.map(node => node.name); + handleDeleteNodes(nodes: INode[]) { + const currentNodes = d3.selectAll('.node'); + if (nodes.length === 0) { + currentNodes.remove(); + return; + } else if (currentNodes.size() > nodes.length) { + const ids = nodes.map(i => i.name); + const deleteNodes = currentNodes.filter((data: any) => { + return !ids.includes(data.name); + }); + deleteNodes.remove(); + return; + } + } + + handleUpdateNodes(nodes: INode[], selectNodes: INode[]) { + const selectNodeIds = selectNodes.map(node => node.uuid); d3.select(this.nodeRef) .selectAll('circle') .data(nodes) .style('fill', (d: INode) => d.color) - .classed('active', (d: INode) => selectNodeNames.includes(d.name)) + .classed('active', (d: INode) => selectNodeIds.includes(d.uuid)) + .attr('id', (d: INode) => `circle-${d.uuid}`) .enter() - .append('circle') + .append('g') + .attr('id', (d: INode) => `node_${d.uuid}`) .attr('class', 'node') + .append('circle') + .attr('class', 'circle') .attr('r', 20) .style('fill', (d: INode) => d.color) // HACK: Color distortion caused by delete node - .attr('id', (d: INode) => `node-${d.uuid}`) .on('mouseover', (d: INode) => { if (this.props.onMouseInNode) { this.props.onMouseInNode(d, d3.event); @@ -277,21 +300,14 @@ class NebulaD3 extends React.Component { this.props.onMouseOut(); } }); - const currentNodes = d3.selectAll('.node'); - if (nodes.length === 0) { - currentNodes.remove(); - return; - } else if (currentNodes.size() > nodes.length) { - const ids = nodes.map(i => i.name); - const deleteNodes = currentNodes.filter((data: any) => { - return !ids.includes(data.name); - }); - deleteNodes.remove(); - return; - } + d3.select(this.nodeRef) + .selectAll('g') + .data(nodes) + .classed('active-node', (d: INode) => selectNodeIds.includes(d.uuid)); + this.node = d3 - .selectAll('.node') + .selectAll('.circle') .on('click', this.handleNodeClick) .on('dblclick', this.props.onDblClickNode) .call( @@ -301,9 +317,45 @@ class NebulaD3 extends React.Component { .on('drag', d => this.dragged(d)) .on('end', this.dragEnded) as any, ); - this.force.on('tick', () => this.tick()); } + handleUpdateIcons = (nodes: INode[]) => { + nodes.forEach(a => { + if (a.icon && !d3 + .select('#node_' + a.uuid) + .select('.icon') + .node()) { + d3.selectAll('#node_' + a.uuid) + .append('text') + .attr('class', 'icon') + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'central') + .attr('stroke', 'black') + .attr('stroke-width', '0.00001%') + .attr('font-family', 'nebula-cloud-icon') + .attr('x', (d: any) => d.x) + .attr('y', (d: any) => d.y) + .attr('id', (d: any) => d.uuid) + .attr('font-size', '20px') + .text(IconCfg.filter(icon => icon.type === a.icon)[0].content); + } + }); + if (d3.selectAll('.icon').node()) { + this.iconText = d3 + .selectAll('.icon') + .on('click', this.handleNodeClick) + .on('dblclick', this.props.onDblClickNode) + .call( + d3 + .drag() + .on('start', d => this.dragstart(d)) + .on('drag', d => this.dragged(d)) + .on('end', this.dragEnded) as any, + ); + } + + }; + handleUpdataNodeTexts = () => { if (this.force) { this.nodeText = d3 @@ -368,7 +420,6 @@ class NebulaD3 extends React.Component { this.force .nodes(data.vertexes) .force('link', linkForce) - // .force('center', d3.forceCenter(width / 2, height / 2)) .restart(); } @@ -444,6 +495,19 @@ class NebulaD3 extends React.Component { }); } + iconRenderText() { + const { data } = this.props; + data.vertexes.forEach((node: any) => { + if (node.nodeProp) { + d3.select('#icon_' + node.uuid) + .append('tspan') + .attr('x', (d: any) => d.x) + .attr('y', (d: any) => d.y) + .attr('dy', '1em'); + } + }); + } + render() { this.computeDataByD3Force(); const { @@ -480,8 +544,8 @@ class NebulaD3 extends React.Component { onMouseOut={onMouseOut} /> (this.nodeRef = ref)} className="nebula-d3-nodes" + ref={(ref: SVGGElement) => (this.nodeRef = ref)} /> void; + handleChangeIconComplete?: (icon: IIcon) => void; +} + +const getColor = (list: INode[], color, customColor) => { + let _color = DEFAULT_COLOR_PICKER; + if (customColor) { + return { + background: customColor, + }; + } + if (list.length > 0) { + const colors = _.uniq(list.map((i: INode) => i.color)); + if (colors.length > 1) { + return { + backgroundImage: DEFAULT_COLOR_MIX, + }; + } else if (colors.length === 1) { + _color = colors[0] || DEFAULT_COLOR_PICKER; + } + } else if (color) { + _color = color; + } + return { + background: _color, + }; +}; + +const getIcon = (list: INode[], icon, customIcon) => { + let _icon = ''; + if (customIcon !== undefined) { + if (customIcon === 'iconimage-iconUnselect') { + customIcon = ''; + } + return customIcon; + } + if (list.length > 0) { + const icons = _.uniq( + list.map((i: INode) => { + return i.icon; + }), + ); + if (icons.length > 1) { + return ''; + } else if (icons.length === 1) { + _icon = icons[0] || ''; + } + } else if (icon) { + _icon = icon; + } + if (_icon === 'iconimage-iconUnselect') { + _icon = ''; + } + return _icon; +}; + +class VertexDisplay extends React.PureComponent { + render() { + const { + showIcon, + currentIcon, + customIcon, + selectVertexes, + customColor, + currentColor, + editing, + } = this.props; + return ( + <> + {selectVertexes?.length !== 0 || editing ? ( + + + + + + + + + } + > +
+
+
+ {showIcon && ( + + )} +
+
+
+
+ ) : ( +
+
+
+ {showIcon && ( + + )} +
+
+
+ )} + + ); + } +} + +export default VertexDisplay; diff --git a/app/assets/config/explore.ts b/app/assets/config/explore.ts index 9ed50d65..35a67879 100644 --- a/app/assets/config/explore.ts +++ b/app/assets/config/explore.ts @@ -222,6 +222,6 @@ export const DEFAULT_EXPLORE_RULES = { edgeDirection: 'outgoing', stepsType: 'single', step: 1, - vertexColor: 'groupByTag', + vertexSets: 'groupByTag', quantityLimit: 100, }; diff --git a/app/assets/config/locale/en-US.json b/app/assets/config/locale/en-US.json index 8f2f3d8a..544f150c 100644 --- a/app/assets/config/locale/en-US.json +++ b/app/assets/config/locale/en-US.json @@ -73,7 +73,8 @@ "show": "Show", "selected": "Selected", "search": "Search", - "color": "Color", + "color": "Color/Icon", + "icon": "Icon", "copy": "Copy", "copySuccess": "Copied to clipboard", "expansionConditions": "Expansion conditions", @@ -117,7 +118,9 @@ "ttlRequired": "Please select the corresponding property, and the data type of the property must be integer or timestamp", "ttlDurationRequired": "Please enter the time (in seconds)", "dataTypeRequired": "Please select the data type", - "fixedStringLength": "Fixed String length must be a positive integer" + "fixedStringLength": "Fixed String length must be a positive integer", + "singleLimitFileData": "Please reduce files to 1M", + "SumLimitFileData": "All files will be over 100M" }, "console": { "cost": "Cost", @@ -137,7 +140,7 @@ "undo": "Undo", "deleteSelectNodes": "Remove Select", "fileImport": "File Import", - "importPlaceholder": "Enter VIDs or enter other data for VID generation, one data per line, and split them by pressing the Enter key. Here is an example:\nstring1\nstring2\nstring3", + "importPlaceholder": "Enter VIDs or enter other data for VID generation, one data per line, and split them by pressing the Enter key. Here is an example:\nplayer100\nplayer101\nteam200\nteam201", "outgoing": "Outgoing", "incoming": "Incoming", "bidirect": "Bidirect", @@ -150,7 +153,7 @@ "showTags": "Show Tags", "showEdges": "Show Edges", "confirm": "Confirm", - "vertexColor": "Vertex Color", + "vertexSets": "Vertex Color", "quantityLimit": "Query Limit", "groupByTag": "Group by vertex tag", "noVertexPrompt": "No vertices on the board. ", @@ -203,7 +206,7 @@ "singleStep": "Single", "rangeStep": "Range", "addCondition": "Add condition", - "customColor": "Custom Color", + "customColor": "Custom Color/Icon", "nodeSearch":"Artboard node search", "searchEmpty": "No data found", "selectedVertexes": "Selected Vertexes", @@ -294,7 +297,8 @@ "indexNotEmpty": "column index can't be null.", "reset": "Reset", "fileUploading": "Upload in progress. If you leave this page now, only the uploaded data are stored.", - "importFinished": "Import task has ended." + "importFinished": "Import task has ended.", + "forbidUpload": "Upload is forbidden" }, "schema": { "spaceList": "Graph Space List", diff --git a/app/assets/config/locale/zh-CN.json b/app/assets/config/locale/zh-CN.json index d49674c1..1762a16c 100644 --- a/app/assets/config/locale/zh-CN.json +++ b/app/assets/config/locale/zh-CN.json @@ -74,7 +74,8 @@ "show": "显示", "selected": "选中", "search": "查询", - "color": "颜色", + "color": "颜色/图标", + "icon": "图标", "copy": "复制", "copySuccess": "已复制到剪切板", "total": "共计", @@ -117,7 +118,9 @@ "ttlRequired": "请选择TTL指定的属性, 且属性的数据类型需为integer或timestamp", "ttlDurationRequired": "请输入时间(s)", "dataTypeRequired": "请选择数据类型", - "fixedStringLength": "Fixed String 长度需为正整数" + "fixedStringLength": "Fixed String 长度需为正整数", + "singleLimitFileData": "单次上传文件大小不得超过1M", + "SumLimitFileData": "文件大小超过100M" }, "console": { "cost": "开销", @@ -137,7 +140,7 @@ "expand": "拓展", "unExpand": "取消拓展", "fileImport": "文件导入", - "importPlaceholder": "输入VID或者用于生成VID的数据,一行一个数据,按回车键断开。格式示例如下:\nstring1\nstring2\nstring3", + "importPlaceholder": "输入VID或者用于生成VID的数据,一行一个数据,按回车键断开。数据示例:\nplayer100\nplayer101\nteam200\nteam201", "outgoing": "流出", "incoming": "流入", "bidirect": "双向", @@ -150,7 +153,7 @@ "showTags": "显示点", "showEdges": "显示边", "confirm": "确定", - "vertexColor": "点的颜色", + "vertexSets": "点的颜色", "quantityLimit": "结果数量限制", "groupByTag": "按标签类型分类", "noVertexPrompt": "当前画板没有点数据,请", @@ -203,7 +206,7 @@ "rangeStep": "范围", "addCondition": "添加条件", "expansionConditions": "拓展条件", - "customColor": "自定义颜色", + "customColor": "自定义颜色/图标", "nodeSearch": "画板节点搜索", "searchEmpty": "未查询到相应数据", "selectedVertexes": "选中的点", @@ -290,7 +293,8 @@ "indexNotEmpty": "对应列标不能为空", "reset": "重置", "fileUploading": "正在上传CSV文件,请等待。如果您离开当前页面,只有已上传的部分数据会被保留。", - "importFinished": "导入任务已结束" + "importFinished": "导入任务已结束", + "forbidUpload": "在线使用,禁止上传" }, "schema": { "spaceList": "图空间列表", diff --git a/app/assets/modules/Explore/NebulaGraph/Menu/index.less b/app/assets/modules/Explore/NebulaGraph/Menu/index.less index 8490fa95..113d7edd 100644 --- a/app/assets/modules/Explore/NebulaGraph/Menu/index.less +++ b/app/assets/modules/Explore/NebulaGraph/Menu/index.less @@ -21,6 +21,20 @@ left: 12px; } + .btn-nodeStyle-set { + position: absolute; + + .btn-color { + position: static; + margin-left: 3px; + } + + .nebula-cloud-icon { + margin-left: 0; + font-size: 16px; + } + } + .panel-menu-icon { position: absolute; diff --git a/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx b/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx index 5039d8a1..730641cc 100644 --- a/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx +++ b/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx @@ -1,7 +1,6 @@ import * as d3 from 'd3'; import React, { Fragment } from 'react'; -import ColorPickerBtn from '../Panel/ColorPicker'; import DeleteBtn from '../Panel/Delete'; import ExpandBtn from '../Panel/Expand'; import Lock from '../Panel/Lock'; @@ -10,6 +9,7 @@ import RollbackBtn from '../Panel/Rollback'; import SearchBtn from '../Panel/Search'; import UnExpandBtn from '../Panel/UnExpandBtn'; import Unlock from '../Panel/Unlock'; +import VertexSet from '../Panel/VertexSet'; import ZoomBtn from '../Panel/Zoom'; import './index.less'; @@ -74,7 +74,7 @@ class Menu extends React.PureComponent { component: , }, { - component: , + component: , }, { component: , diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.less b/app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.less deleted file mode 100644 index 9882f969..00000000 --- a/app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.less +++ /dev/null @@ -1,18 +0,0 @@ - -.btn-picker { - text-align: center; -} - -.btn-color { - width: 25px; - height: 25px; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 3px; - background: #5cdbd3; - margin-right: 10px; - cursor: pointer; -} - -.btn-disabled { - cursor: not-allowed; -} diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.tsx b/app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.tsx deleted file mode 100644 index 23313fef..00000000 --- a/app/assets/modules/Explore/NebulaGraph/Panel/ColorPicker/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import classnames from 'classnames'; -import * as d3 from 'd3'; -import _ from 'lodash'; -import React from 'react'; -import intl from 'react-intl-universal'; -import { connect } from 'react-redux'; - -import MenuButton from '#assets/components/Button'; -import ColorPicker from '#assets/components/ColorPicker'; -import { - DEFAULT_COLOR_MIX, - DEFAULT_COLOR_PICKER, -} from '#assets/config/explore'; -import { IDispatch, IRootState } from '#assets/store'; -import { INode } from '#assets/utils/interface'; - -import './index.less'; - -const mapState = (state: IRootState) => ({ - selectVertexes: state.explore.selectVertexes, - currentColor: state.d3Graph.lastColor, -}); -const mapDispatch = (dispatch: IDispatch) => ({ - updateVertexesColor: (selectVertexes, color) => { - dispatch.explore.update({ - selectVertexes, - }); - dispatch.d3Graph.update({ - lastColor: color, - }); - }, -}); -interface IProps - extends ReturnType, - ReturnType { - onChange?: (color) => void; - showTitle?: string; - customColor?: string; - editing?: boolean; -} - -const getColor = (list: INode[], color, customColor) => { - let _color = DEFAULT_COLOR_PICKER; - if (customColor) { - return { - background: customColor, - }; - } - if (list.length > 0) { - const colors = _.uniq(list.map((i: INode) => i.color)); - if (colors.length > 1) { - return { - backgroundImage: DEFAULT_COLOR_MIX, - }; - } else if (colors.length === 1) { - _color = colors[0] || DEFAULT_COLOR_PICKER; - } - } else if (color) { - _color = color; - } - return { - background: _color, - }; -}; - -const getColorIcon = props => { - const { - selectVertexes, - currentColor, - customColor, - showTitle, - editing, - } = props; - if (showTitle) { - return ( - <> -
- {showTitle && {intl.get('common.color')}} - - ); - } else { - return ( -
- ); - } -}; -class ColorPickerBtn extends React.PureComponent { - handleChangeColorComplete = color => { - const { hex: _color } = color; - if (this.props.onChange) { - this.props.onChange(_color); - } else { - const { selectVertexes, updateVertexesColor } = this.props; - selectVertexes.forEach((vertex: INode) => (vertex.color = _color)); - updateVertexesColor(selectVertexes, _color); - d3.selectAll('.active').style('fill', () => { - return _color; - }); - } - }; - // popover conflict with color picker - // HACK: ColorPicker has no disable attr - render() { - const { - selectVertexes, - currentColor, - showTitle, - customColor, - editing, - } = this.props; - return ( - 0 ? ( - - {getColorIcon({ - selectVertexes, - currentColor, - showTitle, - customColor, - editing, - })} - - ) : ( - getColorIcon({ - selectVertexes, - currentColor, - showTitle, - customColor, - editing, - }) - ) - } - trackCategory="explore" - trackAction="color_picker" - trackLabel="from_panel" - className="menu-color" - tips={!showTitle ? intl.get('common.color') : undefined} - disabled={selectVertexes.length === 0 && !editing} - /> - ); - } -} -export default connect(mapState, mapDispatch)(ColorPickerBtn); diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.less b/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.less new file mode 100644 index 00000000..8ff1746b --- /dev/null +++ b/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.less @@ -0,0 +1,122 @@ +.btn-nodeStyle-set { + display: flex; + align-items: center; + width: 25px; + height: 25px; + margin-right: 10px; + + .color-group { + display: inline-flex; + position: relative; + width: 30px; + + .btn-color { + width: 25px; + height: 25px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.2); + } + + > span { + display: inline-block; + width: 25px; + height: 25px; + border-radius: 3px; + } + + .icon-selected { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + fill: white; + z-index: 10; + width: 16px; + height: 24px; + } + } + + .circle { + display: inline-block; + width: 30px; + height: 30px; + border-radius: 25px; + cursor: pointer; + z-index: 3; + + &:nth-child(2) { + position: absolute; + left: 5px; + z-index: 2; + } + + &:nth-child(3) { + position: absolute; + left: 10px; + z-index: 1; + } + } + + .btn-title { + padding-left: 10px; + } +} + +.nodeStyle-popover { + width: 240px; +} + +.tab-type-set { + .ant-tabs-nav-list { + width: 100%; + + .ant-tabs-tab { + width: 50%; + margin: 0; + padding: 0; + height: 46px; + + .ant-tabs-tab-btn { + width: 100%; + text-align: center; + } + } + } +} + +.icon-group { + padding-bottom: 20px; + height: 240px; + + .icon-box { + display: inline-block; + width: 40px; + height: 40px; + padding: 5px; + background-color: #f3f2f2; + cursor: pointer; + margin: 5px; + } +} + +.ant-carousel .slick-dots-bottom { + bottom: 0; + height: 12px; + margin: 0; + + li { + width: 24px; + } + + li button { + width: 10px; + height: 10px; + border-radius: 10px; + background-color: #cec9c9; + } + + li.slick-active button { + width: 10px; + background-color: gray; + } +} diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx b/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx new file mode 100644 index 00000000..4a423133 --- /dev/null +++ b/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx @@ -0,0 +1,122 @@ +import * as d3 from 'd3'; +import React from 'react'; +import intl from 'react-intl-universal'; +import { connect } from 'react-redux'; + +import MenuButton from '#assets/components/Button'; +import VertexDisplay from '#assets/components/VertexDisplay'; +import { IDispatch, IRootState } from '#assets/store'; +import { INode } from '#assets/utils/interface'; + +import './index.less'; + +const mapState = (state: IRootState) => ({ + selectVertexes: state.explore.selectVertexes, + currentColor: state.d3Graph.lastColor, + currentIcon: state.d3Graph.lastIcon, +}); + +const mapDispatch = (dispatch: IDispatch) => ({ + updateVertexesColor: (selectVertexes, color) => { + dispatch.explore.update({ + selectVertexes, + }); + dispatch.d3Graph.update({ + lastColor: color, + }); + }, + updateVertexesIcon: (selectVertexes, icon) => { + dispatch.explore.update({ + selectVertexes, + }); + dispatch.d3Graph.update({ + lastIcon: icon, + }); + }, +}); + +interface IProps + extends ReturnType, + ReturnType { + showTitle?: boolean; + disabled?: boolean; + showIcon?: boolean; + editing?: boolean; +} +interface IState { + visible: boolean; +} +class VertexSet extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + visible: false, + }; + } + + handleChangeColorComplete = color => { + const { hex: _color } = color; + const { selectVertexes, updateVertexesColor } = this.props; + selectVertexes.forEach((vertex: INode) => (vertex.color = _color)); + updateVertexesColor(selectVertexes, _color); + d3.selectAll('.active').style('fill', () => { + return _color; + }); + }; + + handleChangeIconComplete = icon => { + d3.selectAll('.active-node .icon').remove(); + const { selectVertexes, updateVertexesIcon } = this.props; + selectVertexes.forEach((vertex: INode) => (vertex.icon = icon.type)); + updateVertexesIcon(selectVertexes, icon.type); + + d3.selectAll('.active-node') + .append('text') + .attr('class', 'icon') + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'central') + .attr('stroke', 'black') + .attr('stroke-width', '0.001%') + .attr('font-family', 'nebula-cloud-icon') + .attr('x', (d: any) => d.x) + .attr('y', (d: any) => d.y) + .attr('id', (d: any) => d.uuid) + .attr('font-size', '20px') + .text(icon.content); + }; + + render() { + const { + showTitle, + showIcon, + editing, + selectVertexes, + currentColor, + currentIcon + } = this.props; + return ( + <> + + } + title={showTitle ? intl.get('common.color') : undefined} + trackCategory="explore" + trackAction="color_picker" + trackLabel="from_panel" + /> + + ); + } +} + +export default connect(mapState, mapDispatch)(VertexSet); diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx b/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx index 9b90edc9..4ea626ee 100644 --- a/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx +++ b/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx @@ -1,6 +1,5 @@ import React, { Fragment } from 'react'; -import ColorPickerBtn from './ColorPicker'; import DeleteBtn from './Delete'; import ExpandBtn from './Expand'; import HotkeysDescBtn from './HotKeysDescription'; @@ -12,6 +11,7 @@ import RollbackBtn from './Rollback'; import SearchBtn from './Search'; import UnExpandBtn from './UnExpandBtn'; import Unlock from './Unlock'; +import VertexSet from './VertexSet'; import ZoomBtn from './Zoom'; interface IProps { @@ -96,7 +96,7 @@ class Panel extends React.PureComponent { ], [ { - component: , + component: , }, { component: ( diff --git a/app/assets/modules/Import/Tasks/Upload.tsx b/app/assets/modules/Import/Tasks/Upload.tsx index fd27effa..16465dc8 100644 --- a/app/assets/modules/Import/Tasks/Upload.tsx +++ b/app/assets/modules/Import/Tasks/Upload.tsx @@ -1,11 +1,19 @@ -import { Button, Checkbox, Icon, Modal, Popconfirm, Table, Upload } from 'antd'; +import { + Button, + Checkbox, + Icon, + message, + Modal, + Table, + Tooltip, + Upload, +} from 'antd'; import _ from 'lodash'; import React from 'react'; import intl from 'react-intl-universal'; import { connect } from 'react-redux'; import CSVPreviewLink from '#assets/components/CSVPreviewLink'; -import service from '#assets/config/service'; import { IDispatch, IRootState } from '#assets/store'; import { trackPageView } from '#assets/utils/stat'; @@ -74,15 +82,15 @@ class Import extends React.Component { return file; }; - handleFileDelete = async index => { - const { files } = this.props; - const data: any = await service.deteleFile({ - filename: files[index].name, - }); - if (data.code === 0) { - this.props.updateFiles(files.filter((_, i) => i !== index)); - } - }; + // handleFileDelete = async index => { + // const { files } = this.props; + // const data: any = await service.deteleFile({ + // filename: files[index].name, + // }); + // if (data.code === 0) { + // this.props.updateFiles(files.filter((_, i) => i !== index)); + // } + // }; renderFileTable = () => { const { files, loading } = this.props; @@ -127,7 +135,7 @@ class Import extends React.Component { { title: intl.get('common.operation'), key: 'operation', - render: (_1, file, index) => { + render: (_1, file) => { if (file.content) { return (
@@ -135,6 +143,8 @@ class Import extends React.Component { {intl.get('import.preview')} + {/* + Hack: due to limited resource,can't upload and delete file in this version this.handleFileDelete(index)} title={intl.get('common.ask')} @@ -142,7 +152,7 @@ class Import extends React.Component { cancelText={intl.get('common.cancel')} > - + */}
); @@ -170,6 +180,28 @@ class Import extends React.Component { }; render() { + const props = { + beforeUpload: (file, fileList) => { + let singleSizeSum = 0; + let sizeSum = 0; + fileList.forEach(a => { + singleSizeSum += a.size; + }); + if (singleSizeSum > 1000000) { + message.error(intl.get('formRules.singleLimitFileData')); + return false; + } + const { files } = this.props; + files.forEach(a => { + sizeSum += a.size; + }); + if (sizeSum + file.size > 1000000000) { + message.error(intl.get('formRules.SumLimitFileData')); + return false; + } + return true; + }, + }; const { files } = this.props; return (
@@ -177,6 +209,7 @@ class Import extends React.Component {

{intl.get('import.fileTitle')}

{ action={'/api/files/upload'} onChange={this.handleUploadChange} transformFile={this.transformFile as any} + disabled={true} > - + + +
{this.renderFileTable()} diff --git a/app/assets/static/fonts/iconfont.css b/app/assets/static/fonts/iconfont.css index eb05f11d..862ffbca 100644 --- a/app/assets/static/fonts/iconfont.css +++ b/app/assets/static/fonts/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "nebula-cloud-icon"; /* Project id 1846875 */ - src: url('iconfont.woff2?t=1627440484426') format('woff2'), - url('iconfont.woff?t=1627440484426') format('woff'), - url('iconfont.ttf?t=1627440484426') format('truetype'); + src: url('iconfont.woff2?t=1631006849129') format('woff2'), + url('iconfont.woff?t=1631006849129') format('woff'), + url('iconfont.ttf?t=1631006849129') format('truetype'); } .nebula-cloud-icon { @@ -13,6 +13,126 @@ -moz-osx-font-smoothing: grayscale; } +.iconimage-iconUnselect:before { + content: "\e71a"; +} + +.iconimage-icon17:before { + content: "\e70a"; +} + +.iconimage-icon16:before { + content: "\e70b"; +} + +.iconimage-icon21:before { + content: "\e70c"; +} + +.iconimage-icon20:before { + content: "\e70d"; +} + +.iconimage-icon15:before { + content: "\e70e"; +} + +.iconimage-icon22:before { + content: "\e70f"; +} + +.iconimage-icon19:before { + content: "\e710"; +} + +.iconimage-icon23:before { + content: "\e711"; +} + +.iconimage-icon18:before { + content: "\e712"; +} + +.iconimage-icon25:before { + content: "\e713"; +} + +.iconimage-icon14:before { + content: "\e714"; +} + +.iconimage-icon24:before { + content: "\e715"; +} + +.iconimage-icon26:before { + content: "\e716"; +} + +.iconimage-icon28:before { + content: "\e717"; +} + +.iconimage-icon29:before { + content: "\e718"; +} + +.iconimage-icon27:before { + content: "\e719"; +} + +.iconimage-icon1:before { + content: "\e6fd"; +} + +.iconimage-icon3:before { + content: "\e6fe"; +} + +.iconimage-icon2:before { + content: "\e6ff"; +} + +.iconimage-icon4:before { + content: "\e700"; +} + +.iconimage-icon5:before { + content: "\e701"; +} + +.iconimage-icon6:before { + content: "\e702"; +} + +.iconimage-icon7:before { + content: "\e703"; +} + +.iconimage-icon8:before { + content: "\e704"; +} + +.iconimage-icon9:before { + content: "\e705"; +} + +.iconimage-icon10:before { + content: "\e706"; +} + +.iconimage-icon12:before { + content: "\e707"; +} + +.iconimage-icon13:before { + content: "\e708"; +} + +.iconimage-icon11:before { + content: "\e709"; +} + .iconstudio-unexpand:before { content: "\e67d"; } diff --git a/app/assets/static/fonts/iconfont.ttf b/app/assets/static/fonts/iconfont.ttf index 8643e1928909cb13a3664bca7358afa29ad234a5..014770f12b68457ad885b55ac02c48ac4e6ff9a4 100644 GIT binary patch delta 9376 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 diff --git a/app/assets/static/fonts/iconfont.woff b/app/assets/static/fonts/iconfont.woff index d983665ae09c6e1dc8ab8f72ca6bf6a8df6d791a..6d8ac6e58772999db5e4f148c8034643eb7664ad 100644 GIT binary patch delta 22447 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 delta 17627 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 Ol3J}dCOw-+0000R6Qt+> diff --git a/app/assets/static/fonts/iconfont.woff2 b/app/assets/static/fonts/iconfont.woff2 index 1b91157b80c317f670456f44066adc90587ad241..11629b113d279434c7e84d17715c350cb0b72e0c 100644 GIT binary patch literal 19260 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 literal 15388 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 G0000w#G3*D diff --git a/app/assets/store/models/d3Graph.ts b/app/assets/store/models/d3Graph.ts index 8a54277a..3acaa64f 100644 --- a/app/assets/store/models/d3Graph.ts +++ b/app/assets/store/models/d3Graph.ts @@ -8,6 +8,7 @@ interface IState { showDisplayPanel: boolean; isZoom: boolean; lastColor: string; + lastIcon: string; } export const d3Graph = createModel({ @@ -18,6 +19,7 @@ export const d3Graph = createModel({ showSider: true, showDisplayPanel: true, lastColor: '', + lastIcon: '', isZoom: false, }, reducers: { diff --git a/app/assets/store/models/explore.ts b/app/assets/store/models/explore.ts index 4ec257d2..fb7f6836 100644 --- a/app/assets/store/models/explore.ts +++ b/app/assets/store/models/explore.ts @@ -54,13 +54,14 @@ interface IExportData { interface IRules { edgeTypes?: string[]; edgeDirection?: string; - vertexColor?: string; + vertexSets?: string; quantityLimit?: number; stepsType?: string; step?: number; minStep?: number; maxStep?: number; customColor?: string; + customIcon?: string; filters?: any[]; } @@ -99,9 +100,11 @@ function getTagData(nodes, expand) { const { vid, tags, properties } = node; const group = getGroup(tags); const color = - expand && expand.customColor && expand.vertexColor === 'custom' + expand?.vertexSets === 'custom' ? expand.customColor : whichColor(group); + const icon = + expand?.vertexSets === 'custom' ? expand.customIcon : ''; const nodeProp = { tags, properties, @@ -113,6 +116,7 @@ function getTagData(nodes, expand) { group, uuid, color, + icon, }; }); return data; @@ -210,8 +214,9 @@ export const explore = createModel({ payload: { ids: string[]; expand?: { - vertexColor: string; + vertexSets: string; customColor; + customIcon; }; }, rootState, @@ -323,21 +328,23 @@ export const explore = createModel({ edgesFields: any[]; edgeDirection: string; filters: any[]; - vertexColor: string; + vertexSets: string; quantityLimit: number | null; stepsType: string; step?: string; minStep?: string; maxStep?: string; customColor?: string; + customIcon?: string; }) { const data = (await this.asyncGetExpandData(payload)) as any; - const { vertexColor, customColor } = payload; + const { vertexSets, customColor, customIcon } = payload; await this.asyncGetExploreInfo({ data, expand: { - vertexColor, + vertexSets, customColor, + customIcon, }, }); }, @@ -359,8 +366,9 @@ export const explore = createModel({ async asyncGetExploreVertex(payload: { ids: string[]; expand?: { - vertexColor: string; + vertexSets: string; customColor; + customIcon; }; }) { const { ids, expand } = payload; @@ -418,7 +426,7 @@ export const explore = createModel({ }, uuid: uuidv4(), }; - if (expand && expand.vertexColor === 'groupByTag') { + if (expand && expand.vertexSets === 'groupByTag') { vertex.group = 't'; } preAddVertexes.push(vertex); @@ -488,8 +496,9 @@ export const explore = createModel({ async asyncGetExploreInfo(payload: { data: IExportData; expand?: { - vertexColor: string; + vertexSets: string; customColor; + customIcon; }; }) { const { data, expand } = payload; @@ -523,12 +532,13 @@ export const explore = createModel({ return message.warning(intl.get('explore.missingParams')); } const data = (await this.asyncGetExpandData(rules)) as any; - const { vertexColor, customColor } = rules; + const { vertexSets, customColor, customIcon } = rules; await this.asyncGetExploreInfo({ data, expand: { - vertexColor, + vertexSets, customColor, + customIcon, }, }); }, @@ -540,7 +550,7 @@ export const explore = createModel({ edgesFields?: any[]; edgeDirection: string; filters?: any[]; - vertexColor?: string; + vertexSets?: string; quantityLimit?: number | null; stepsType?: string; step?: string; diff --git a/app/assets/utils/interface.ts b/app/assets/utils/interface.ts index bbc53665..1f1607e9 100644 --- a/app/assets/utils/interface.ts +++ b/app/assets/utils/interface.ts @@ -4,6 +4,7 @@ export interface INode extends d3.SimulationNodeDatum { group: number; uuid: string; color: string; + icon: string; } export interface IPath extends d3.SimulationLinkDatum { From 93cdceda2f963ac686db6cf3e16ff45fc8e21d84 Mon Sep 17 00:00:00 2001 From: kun Date: Tue, 28 Sep 2021 19:36:44 +0800 Subject: [PATCH 2/8] mod: delete useless code --- app/assets/modules/Import/Tasks/Upload.tsx | 57 ++++++---------------- 1 file changed, 16 insertions(+), 41 deletions(-) diff --git a/app/assets/modules/Import/Tasks/Upload.tsx b/app/assets/modules/Import/Tasks/Upload.tsx index 16465dc8..99443a4e 100644 --- a/app/assets/modules/Import/Tasks/Upload.tsx +++ b/app/assets/modules/Import/Tasks/Upload.tsx @@ -7,6 +7,7 @@ import { Table, Tooltip, Upload, + Popconfirm, } from 'antd'; import _ from 'lodash'; import React from 'react'; @@ -14,6 +15,7 @@ import intl from 'react-intl-universal'; import { connect } from 'react-redux'; import CSVPreviewLink from '#assets/components/CSVPreviewLink'; +import service from '#assets/config/service'; import { IDispatch, IRootState } from '#assets/store'; import { trackPageView } from '#assets/utils/stat'; @@ -82,15 +84,15 @@ class Import extends React.Component { return file; }; - // handleFileDelete = async index => { - // const { files } = this.props; - // const data: any = await service.deteleFile({ - // filename: files[index].name, - // }); - // if (data.code === 0) { - // this.props.updateFiles(files.filter((_, i) => i !== index)); - // } - // }; + handleFileDelete = async index => { + const { files } = this.props; + const data: any = await service.deteleFile({ + filename: files[index].name, + }); + if (data.code === 0) { + this.props.updateFiles(files.filter((_, i) => i !== index)); + } + }; renderFileTable = () => { const { files, loading } = this.props; @@ -135,7 +137,7 @@ class Import extends React.Component { { title: intl.get('common.operation'), key: 'operation', - render: (_1, file) => { + render: (_1, file,index) => { if (file.content) { return (
@@ -143,8 +145,6 @@ class Import extends React.Component { {intl.get('import.preview')} - {/* - Hack: due to limited resource,can't upload and delete file in this version this.handleFileDelete(index)} title={intl.get('common.ask')} @@ -152,7 +152,7 @@ class Import extends React.Component { cancelText={intl.get('common.cancel')} > - */} +
); @@ -180,28 +180,6 @@ class Import extends React.Component { }; render() { - const props = { - beforeUpload: (file, fileList) => { - let singleSizeSum = 0; - let sizeSum = 0; - fileList.forEach(a => { - singleSizeSum += a.size; - }); - if (singleSizeSum > 1000000) { - message.error(intl.get('formRules.singleLimitFileData')); - return false; - } - const { files } = this.props; - files.forEach(a => { - sizeSum += a.size; - }); - if (sizeSum + file.size > 1000000000) { - message.error(intl.get('formRules.SumLimitFileData')); - return false; - } - return true; - }, - }; const { files } = this.props; return (
@@ -209,7 +187,6 @@ class Import extends React.Component {

{intl.get('import.fileTitle')}

{ transformFile={this.transformFile as any} disabled={true} > - - - +
{this.renderFileTable()} From bf1e44e734f258444b801297d665c4e6778b616a Mon Sep 17 00:00:00 2001 From: kun Date: Tue, 28 Sep 2021 19:48:44 +0800 Subject: [PATCH 3/8] mod: modify text --- app/assets/components/Expand/ExpandForm/CustomSet.tsx | 2 +- app/assets/config/locale/en-US.json | 10 ++++------ app/assets/config/locale/zh-CN.json | 10 ++++------ .../Explore/NebulaGraph/Panel/VertexSet/index.tsx | 4 ++-- app/assets/modules/Import/Tasks/Upload.tsx | 5 ++--- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/app/assets/components/Expand/ExpandForm/CustomSet.tsx b/app/assets/components/Expand/ExpandForm/CustomSet.tsx index 115cf83f..c5c01433 100644 --- a/app/assets/components/Expand/ExpandForm/CustomSet.tsx +++ b/app/assets/components/Expand/ExpandForm/CustomSet.tsx @@ -32,7 +32,7 @@ class CustomSet extends React.PureComponent { return ( <> { return ( <> { currentIcon={currentIcon} /> } - title={showTitle ? intl.get('common.color') : undefined} + title={showTitle ? intl.get('common.vertexSets') : undefined} trackCategory="explore" trackAction="color_picker" trackLabel="from_panel" diff --git a/app/assets/modules/Import/Tasks/Upload.tsx b/app/assets/modules/Import/Tasks/Upload.tsx index 99443a4e..373627bb 100644 --- a/app/assets/modules/Import/Tasks/Upload.tsx +++ b/app/assets/modules/Import/Tasks/Upload.tsx @@ -137,7 +137,7 @@ class Import extends React.Component { { title: intl.get('common.operation'), key: 'operation', - render: (_1, file,index) => { + render: (_1, file, index) => { if (file.content) { return (
@@ -194,9 +194,8 @@ class Import extends React.Component { action={'/api/files/upload'} onChange={this.handleUploadChange} transformFile={this.transformFile as any} - disabled={true} > - From 5f886b9715be50413f83ed4423f3db3158326ede Mon Sep 17 00:00:00 2001 From: kun Date: Tue, 28 Sep 2021 19:54:37 +0800 Subject: [PATCH 4/8] mod: modify icon text --- app/assets/config/locale/en-US.json | 4 ++-- app/assets/config/locale/zh-CN.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/config/locale/en-US.json b/app/assets/config/locale/en-US.json index 2e877667..612623f9 100644 --- a/app/assets/config/locale/en-US.json +++ b/app/assets/config/locale/en-US.json @@ -119,7 +119,7 @@ "ttlRequired": "Please select the corresponding property, and the data type of the property must be integer or timestamp", "ttlDurationRequired": "Please enter the time (in seconds)", "dataTypeRequired": "Please select the data type", - "fixedStringLength": "Fixed String length must be a positive integer", + "fixedStringLength": "Fixed String length must be a positive integer" }, "console": { "cost": "Cost", @@ -139,7 +139,7 @@ "undo": "Undo", "deleteSelectNodes": "Remove Select", "fileImport": "File Import", - "importPlaceholder": "Enter VIDs or enter other data for VID generation, one data per line, and split them by pressing the Enter key. ", + "importPlaceholder": "Enter VIDs or enter other data for VID generation, one data per line, and split them by pressing the Enter key. Here is an example:\nstring1\nstring2\nstring3", "outgoing": "Outgoing", "incoming": "Incoming", "bidirect": "Bidirect", diff --git a/app/assets/config/locale/zh-CN.json b/app/assets/config/locale/zh-CN.json index 1219f0ed..220d7331 100644 --- a/app/assets/config/locale/zh-CN.json +++ b/app/assets/config/locale/zh-CN.json @@ -139,7 +139,7 @@ "expand": "拓展", "unExpand": "取消拓展", "fileImport": "文件导入", - "importPlaceholder": "输入VID或者用于生成VID的数据,一行一个数据,按回车键断开。", + "importPlaceholder": "输入VID或者用于生成VID的数据,一行一个数据,按回车键断开。格式示例如下:\nstring1\nstring2\nstring3", "outgoing": "流出", "incoming": "流入", "bidirect": "双向", @@ -292,7 +292,7 @@ "indexNotEmpty": "对应列标不能为空", "reset": "重置", "fileUploading": "正在上传CSV文件,请等待。如果您离开当前页面,只有已上传的部分数据会被保留。", - "importFinished": "导入任务已结束", + "importFinished": "导入任务已结束" }, "schema": { "spaceList": "图空间列表", From 0c6d88a5d7c6312e6aa6201b1d10367e1db3d76e Mon Sep 17 00:00:00 2001 From: kun Date: Wed, 29 Sep 2021 11:54:08 +0800 Subject: [PATCH 5/8] mod: delete useless import --- app/assets/components/IconPicker/index.tsx | 2 +- app/assets/modules/Import/Tasks/Upload.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/components/IconPicker/index.tsx b/app/assets/components/IconPicker/index.tsx index e4d1abe6..0449f905 100644 --- a/app/assets/components/IconPicker/index.tsx +++ b/app/assets/components/IconPicker/index.tsx @@ -18,7 +18,7 @@ const iconGroup = chunk(IconCfg, 16); function IconItem({ icon, onClick }: { icon: T; onClick: (icon: T) => void; }) { const { type, content } = icon; - const iconElement = onClick(icon)} />; + const iconElement = onClick(icon)} />; return (
diff --git a/app/assets/modules/Import/Tasks/Upload.tsx b/app/assets/modules/Import/Tasks/Upload.tsx index 373627bb..580ca5e4 100644 --- a/app/assets/modules/Import/Tasks/Upload.tsx +++ b/app/assets/modules/Import/Tasks/Upload.tsx @@ -2,10 +2,8 @@ import { Button, Checkbox, Icon, - message, Modal, Table, - Tooltip, Upload, Popconfirm, } from 'antd'; From e1ef1c4ec2051e3c4c2f36cba619d52a8a233812 Mon Sep 17 00:00:00 2001 From: kun Date: Wed, 29 Sep 2021 17:45:02 +0800 Subject: [PATCH 6/8] mod: delete console.err --- app/assets/components/Expand/ExpandForm/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/components/Expand/ExpandForm/index.tsx b/app/assets/components/Expand/ExpandForm/index.tsx index 0046c35a..9db81aa1 100644 --- a/app/assets/components/Expand/ExpandForm/index.tsx +++ b/app/assets/components/Expand/ExpandForm/index.tsx @@ -146,7 +146,6 @@ class Expand extends React.Component { const { filters, customColor, customIcon } = this.state; this.props.form.validateFields(async err => { if (err) { - console.error(err); return; } const { From b716358700fa2024d29cb2e26174a776fd417de0 Mon Sep 17 00:00:00 2001 From: Nut He <18328704+hetao92@users.noreply.github.com> Date: Sat, 2 Oct 2021 15:15:43 +0800 Subject: [PATCH 7/8] mod: remove last color logic --- app/assets/components/Button/index.less | 4 +- app/assets/components/Button/index.tsx | 13 +- app/assets/components/ColorPicker/index.tsx | 6 +- .../Expand/ExpandForm/CustomSet.tsx | 55 ------ .../components/Expand/ExpandForm/index.tsx | 13 +- app/assets/components/IconPicker/index.tsx | 35 +++- app/assets/components/NebulaD3/index.tsx | 34 ++-- app/assets/components/VertexDisplay/index.tsx | 160 ------------------ .../components/VertexStyleSet/DisplayBtn.tsx | 30 ++++ .../VertexStyleSet/StyleSetTabs.tsx | 34 ++++ .../components/VertexStyleSet/index.tsx | 46 +++++ .../Explore/NebulaGraph/Menu/index.less | 14 +- .../Explore/NebulaGraph/Menu/index.tsx | 4 +- .../NebulaGraph/Panel/VertexSet/index.tsx | 122 ------------- .../index.less | 0 .../Panel/VertexStyleSetBtn/index.tsx | 116 +++++++++++++ .../Explore/NebulaGraph/Panel/index.tsx | 4 +- app/assets/modules/Import/Tasks/Upload.tsx | 10 +- app/assets/store/models/d3Graph.ts | 4 - app/assets/store/models/explore.ts | 7 +- 20 files changed, 306 insertions(+), 405 deletions(-) delete mode 100644 app/assets/components/Expand/ExpandForm/CustomSet.tsx delete mode 100644 app/assets/components/VertexDisplay/index.tsx create mode 100644 app/assets/components/VertexStyleSet/DisplayBtn.tsx create mode 100644 app/assets/components/VertexStyleSet/StyleSetTabs.tsx create mode 100644 app/assets/components/VertexStyleSet/index.tsx delete mode 100644 app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx rename app/assets/modules/Explore/NebulaGraph/Panel/{VertexSet => VertexStyleSetBtn}/index.less (100%) create mode 100644 app/assets/modules/Explore/NebulaGraph/Panel/VertexStyleSetBtn/index.tsx diff --git a/app/assets/components/Button/index.less b/app/assets/components/Button/index.less index 9e99e816..dfa40507 100644 --- a/app/assets/components/Button/index.less +++ b/app/assets/components/Button/index.less @@ -23,7 +23,5 @@ } .panel-actived { - svg { - fill: #0091ff; - } + color: #0091ff; } diff --git a/app/assets/components/Button/index.tsx b/app/assets/components/Button/index.tsx index ba25016b..164f4e96 100644 --- a/app/assets/components/Button/index.tsx +++ b/app/assets/components/Button/index.tsx @@ -114,7 +114,18 @@ const CustomizeTooltipBtn = (props: IMenuButton) => { onClick={!disabled ? action : undefined} /> ) : ( - component +
+ {component} +
)} ); diff --git a/app/assets/components/ColorPicker/index.tsx b/app/assets/components/ColorPicker/index.tsx index 2103fe4a..e0dec0fe 100644 --- a/app/assets/components/ColorPicker/index.tsx +++ b/app/assets/components/ColorPicker/index.tsx @@ -25,13 +25,15 @@ class ColorPicker extends React.Component { handleChange = color => { if (this.props.handleChange) { - this.props.handleChange(color); + const { hex: _color } = color; + this.props.handleChange(_color); } }; handleChangeComplete = (color, _event) => { if (this.props.handleChangeColorComplete) { - this.props.handleChangeColorComplete(color); + const { hex: _color } = color; + this.props.handleChangeColorComplete(_color); } }; diff --git a/app/assets/components/Expand/ExpandForm/CustomSet.tsx b/app/assets/components/Expand/ExpandForm/CustomSet.tsx deleted file mode 100644 index c5c01433..00000000 --- a/app/assets/components/Expand/ExpandForm/CustomSet.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import intl from 'react-intl-universal'; -import MenuButton from '#assets/components/Button'; -import VertexDisplay from '#assets/components/VertexDisplay'; - -interface IProps { - showTitle?: boolean; - showIcon?: boolean; - customColor?: string; - customIcon?: string; - onColorChange?: (color: string) => void; - onIconChange?: (icon: string) => void; -} - -class CustomSet extends React.PureComponent { - handleChangeColorComplete = color => { - const { hex: _color } = color; - this.props.onColorChange?.(_color); - }; - - handleChangeIconComplete = icon => { - this.props.onIconChange?.(icon); - }; - - render() { - const { - showTitle, - showIcon, - customIcon, - customColor, - } = this.props; - return ( - <> - - } - trackCategory="explore" - trackAction="color_picker" - trackLabel="from_panel" - /> - - ) - } -} - -export default CustomSet; \ No newline at end of file diff --git a/app/assets/components/Expand/ExpandForm/index.tsx b/app/assets/components/Expand/ExpandForm/index.tsx index 9db81aa1..fb6cd388 100644 --- a/app/assets/components/Expand/ExpandForm/index.tsx +++ b/app/assets/components/Expand/ExpandForm/index.tsx @@ -16,8 +16,8 @@ import { connect } from 'react-redux'; import { Instruction } from '#assets/components'; import GQLModal from '#assets/components/GQLModal'; import IconFont from '#assets/components/Icon'; +import VertexStyleSet from '#assets/components/VertexStyleSet'; import { DEFAULT_COLOR_PICKER } from '#assets/config/explore'; -import CustomSet from './CustomSet'; import { IDispatch, IRootState } from '#assets/store'; import { RELATION_OPERATORS } from '#assets/utils/constant'; import { getExploreMatchGQL } from '#assets/utils/gql'; @@ -457,12 +457,11 @@ class Expand extends React.Component { {intl.get('explore.customColor')} - , )} diff --git a/app/assets/components/IconPicker/index.tsx b/app/assets/components/IconPicker/index.tsx index 0449f905..99a0e2e5 100644 --- a/app/assets/components/IconPicker/index.tsx +++ b/app/assets/components/IconPicker/index.tsx @@ -1,13 +1,15 @@ import { Carousel, Popover } from 'antd'; import { chunk } from 'lodash'; import React from 'react'; + import Icon from '#assets/components/Icon'; + import IconCfg from './iconCfg'; import './index.less'; interface IIcon { type: string; - content: string + content: string; } interface IProps { @@ -16,31 +18,46 @@ interface IProps { const iconGroup = chunk(IconCfg, 16); -function IconItem({ icon, onClick }: { icon: T; onClick: (icon: T) => void; }) { - const { type, content } = icon; - const iconElement = onClick(icon)} />; +interface IIconItem { + onClick: (icon: IIcon) => void; + icon: IIcon; +} + +const IconItem = (props: IIconItem) => { + const { + onClick, + icon: { type, content }, + } = props; + const iconElement = ( + onClick(props.icon)} /> + ); return (
- {!!content ? iconElement : {iconElement}} + {!!content ? ( + iconElement + ) : ( + {iconElement} + )}
); -} +}; class IconPickerBtn extends React.PureComponent { handleChangeIconComplete = (icon: IIcon) => { - this.props.handleChangeIconComplete?.(icon); + this.props.handleChangeIconComplete?.(icon); }; render() { return (
- + {iconGroup.map((group, index) => (
{group.map(icon => ( - ))} diff --git a/app/assets/components/NebulaD3/index.tsx b/app/assets/components/NebulaD3/index.tsx index 91eede6e..e96def82 100644 --- a/app/assets/components/NebulaD3/index.tsx +++ b/app/assets/components/NebulaD3/index.tsx @@ -321,11 +321,14 @@ class NebulaD3 extends React.Component { handleUpdateIcons = (nodes: INode[]) => { nodes.forEach(a => { - if (a.icon && !d3 - .select('#node_' + a.uuid) - .select('.icon') - .node()) { - d3.selectAll('#node_' + a.uuid) + if ( + a.icon && + !d3 + .select('#node_' + a.uuid) + .select('.icon') + .node() + ) { + d3.selectAll('#node_' + a.uuid) .append('text') .attr('class', 'icon') .attr('text-anchor', 'middle') @@ -342,18 +345,17 @@ class NebulaD3 extends React.Component { }); if (d3.selectAll('.icon').node()) { this.iconText = d3 - .selectAll('.icon') - .on('click', this.handleNodeClick) - .on('dblclick', this.props.onDblClickNode) - .call( - d3 - .drag() - .on('start', d => this.dragstart(d)) - .on('drag', d => this.dragged(d)) - .on('end', this.dragEnded) as any, - ); + .selectAll('.icon') + .on('click', this.handleNodeClick) + .on('dblclick', this.props.onDblClickNode) + .call( + d3 + .drag() + .on('start', d => this.dragstart(d)) + .on('drag', d => this.dragged(d)) + .on('end', this.dragEnded) as any, + ); } - }; handleUpdataNodeTexts = () => { diff --git a/app/assets/components/VertexDisplay/index.tsx b/app/assets/components/VertexDisplay/index.tsx deleted file mode 100644 index a51f2bda..00000000 --- a/app/assets/components/VertexDisplay/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { Popover, Tabs } from 'antd'; -import classnames from 'classnames'; -import _ from 'lodash'; -import React from 'react'; -import intl from 'react-intl-universal'; - -import ColorPicker from '#assets/components/ColorPicker'; -import Icon from '#assets/components/Icon'; -import IconPicker from '#assets/components/IconPicker'; -import { - DEFAULT_COLOR_MIX, - DEFAULT_COLOR_PICKER, -} from '#assets/config/explore'; -import { INode } from '#assets/utils/interface'; - -interface IIcon { - type: string; - content: string -} - -interface IProps{ - disabled?: boolean; - showIcon?: boolean; - editing?: boolean; - customColor?: string; - customIcon?: string; - selectVertexes?: any; - currentColor?: string; - currentIcon?: string; - handleChangeColorComplete: (color: string) => void; - handleChangeIconComplete?: (icon: IIcon) => void; -} - -const getColor = (list: INode[], color, customColor) => { - let _color = DEFAULT_COLOR_PICKER; - if (customColor) { - return { - background: customColor, - }; - } - if (list.length > 0) { - const colors = _.uniq(list.map((i: INode) => i.color)); - if (colors.length > 1) { - return { - backgroundImage: DEFAULT_COLOR_MIX, - }; - } else if (colors.length === 1) { - _color = colors[0] || DEFAULT_COLOR_PICKER; - } - } else if (color) { - _color = color; - } - return { - background: _color, - }; -}; - -const getIcon = (list: INode[], icon, customIcon) => { - let _icon = ''; - if (customIcon !== undefined) { - if (customIcon === 'iconimage-iconUnselect') { - customIcon = ''; - } - return customIcon; - } - if (list.length > 0) { - const icons = _.uniq( - list.map((i: INode) => { - return i.icon; - }), - ); - if (icons.length > 1) { - return ''; - } else if (icons.length === 1) { - _icon = icons[0] || ''; - } - } else if (icon) { - _icon = icon; - } - if (_icon === 'iconimage-iconUnselect') { - _icon = ''; - } - return _icon; -}; - -class VertexDisplay extends React.PureComponent { - render() { - const { - showIcon, - currentIcon, - customIcon, - selectVertexes, - customColor, - currentColor, - editing, - } = this.props; - return ( - <> - {selectVertexes?.length !== 0 || editing ? ( - - - - - - - - - } - > -
-
-
- {showIcon && ( - - )} -
-
-
-
- ) : ( -
-
-
- {showIcon && ( - - )} -
-
-
- )} - - ); - } -} - -export default VertexDisplay; diff --git a/app/assets/components/VertexStyleSet/DisplayBtn.tsx b/app/assets/components/VertexStyleSet/DisplayBtn.tsx new file mode 100644 index 00000000..d8f93e10 --- /dev/null +++ b/app/assets/components/VertexStyleSet/DisplayBtn.tsx @@ -0,0 +1,30 @@ +import classnames from 'classnames'; +import _ from 'lodash'; +import React from 'react'; + +import Icon from '#assets/components/Icon'; + +interface IProps { + icon?: string; + color: string; +} + +class DisplayBtn extends React.PureComponent { + render() { + const { icon, color } = this.props; + return ( +
+
+
+ {icon && } +
+
+
+ ); + } +} + +export default DisplayBtn; diff --git a/app/assets/components/VertexStyleSet/StyleSetTabs.tsx b/app/assets/components/VertexStyleSet/StyleSetTabs.tsx new file mode 100644 index 00000000..f6674aee --- /dev/null +++ b/app/assets/components/VertexStyleSet/StyleSetTabs.tsx @@ -0,0 +1,34 @@ +import { Tabs } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import intl from 'react-intl-universal'; + +import ColorPicker from '#assets/components/ColorPicker'; +import IconPicker from '#assets/components/IconPicker'; +interface IIcon { + type: string; + content: string; +} + +interface IProps { + handleChangeColorComplete: (color: string) => void; + handleChangeIconComplete: (icon: IIcon) => void; +} + +class StyleSetTabs extends React.PureComponent { + render() { + const { handleChangeColorComplete, handleChangeIconComplete } = this.props; + return ( + + + + + + + + + ); + } +} + +export default StyleSetTabs; diff --git a/app/assets/components/VertexStyleSet/index.tsx b/app/assets/components/VertexStyleSet/index.tsx new file mode 100644 index 00000000..c0bfde66 --- /dev/null +++ b/app/assets/components/VertexStyleSet/index.tsx @@ -0,0 +1,46 @@ +import { Popover } from 'antd'; +import _ from 'lodash'; +import React from 'react'; + +import DisplayBtn from './DisplayBtn'; +import StyleSetTabs from './StyleSetTabs'; +interface IIcon { + type: string; + content: string; +} + +interface IProps { + icon?: string; + color: string; + handleChangeColorComplete: (color: string) => void; + handleChangeIconComplete: (icon: IIcon) => void; +} + +class VertexStyleSet extends React.PureComponent { + render() { + const { + icon, + color, + handleChangeColorComplete, + handleChangeIconComplete, + } = this.props; + return ( + + } + > +
+ +
+
+ ); + } +} + +export default VertexStyleSet; diff --git a/app/assets/modules/Explore/NebulaGraph/Menu/index.less b/app/assets/modules/Explore/NebulaGraph/Menu/index.less index 113d7edd..d32578e6 100644 --- a/app/assets/modules/Explore/NebulaGraph/Menu/index.less +++ b/app/assets/modules/Explore/NebulaGraph/Menu/index.less @@ -21,9 +21,13 @@ left: 12px; } - .btn-nodeStyle-set { - position: absolute; + .nebula-cloud-icon { + width: auto; + margin-left: 2px; + font-size: 20px; + } + .btn-nodeStyle-set { .btn-color { position: static; margin-left: 3px; @@ -51,12 +55,6 @@ letter-spacing: 0; font-weight: 400; } - - .nebula-cloud-icon { - width: auto; - margin-left: 2px; - font-size: 20px; - } } .panel-btn-item:not(:first-child) { diff --git a/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx b/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx index 730641cc..df4d8202 100644 --- a/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx +++ b/app/assets/modules/Explore/NebulaGraph/Menu/index.tsx @@ -9,7 +9,7 @@ import RollbackBtn from '../Panel/Rollback'; import SearchBtn from '../Panel/Search'; import UnExpandBtn from '../Panel/UnExpandBtn'; import Unlock from '../Panel/Unlock'; -import VertexSet from '../Panel/VertexSet'; +import VertexStyleSetBtn from '../Panel/VertexStyleSetBtn'; import ZoomBtn from '../Panel/Zoom'; import './index.less'; @@ -74,7 +74,7 @@ class Menu extends React.PureComponent { component: , }, { - component: , + component: , }, { component: , diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx b/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx deleted file mode 100644 index 223d887d..00000000 --- a/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import * as d3 from 'd3'; -import React from 'react'; -import intl from 'react-intl-universal'; -import { connect } from 'react-redux'; - -import MenuButton from '#assets/components/Button'; -import VertexDisplay from '#assets/components/VertexDisplay'; -import { IDispatch, IRootState } from '#assets/store'; -import { INode } from '#assets/utils/interface'; - -import './index.less'; - -const mapState = (state: IRootState) => ({ - selectVertexes: state.explore.selectVertexes, - currentColor: state.d3Graph.lastColor, - currentIcon: state.d3Graph.lastIcon, -}); - -const mapDispatch = (dispatch: IDispatch) => ({ - updateVertexesColor: (selectVertexes, color) => { - dispatch.explore.update({ - selectVertexes, - }); - dispatch.d3Graph.update({ - lastColor: color, - }); - }, - updateVertexesIcon: (selectVertexes, icon) => { - dispatch.explore.update({ - selectVertexes, - }); - dispatch.d3Graph.update({ - lastIcon: icon, - }); - }, -}); - -interface IProps - extends ReturnType, - ReturnType { - showTitle?: boolean; - disabled?: boolean; - showIcon?: boolean; - editing?: boolean; -} -interface IState { - visible: boolean; -} -class VertexSet extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - visible: false, - }; - } - - handleChangeColorComplete = color => { - const { hex: _color } = color; - const { selectVertexes, updateVertexesColor } = this.props; - selectVertexes.forEach((vertex: INode) => (vertex.color = _color)); - updateVertexesColor(selectVertexes, _color); - d3.selectAll('.active').style('fill', () => { - return _color; - }); - }; - - handleChangeIconComplete = icon => { - d3.selectAll('.active-node .icon').remove(); - const { selectVertexes, updateVertexesIcon } = this.props; - selectVertexes.forEach((vertex: INode) => (vertex.icon = icon.type)); - updateVertexesIcon(selectVertexes, icon.type); - - d3.selectAll('.active-node') - .append('text') - .attr('class', 'icon') - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'central') - .attr('stroke', 'black') - .attr('stroke-width', '0.001%') - .attr('font-family', 'nebula-cloud-icon') - .attr('x', (d: any) => d.x) - .attr('y', (d: any) => d.y) - .attr('id', (d: any) => d.uuid) - .attr('font-size', '20px') - .text(icon.content); - }; - - render() { - const { - showTitle, - showIcon, - editing, - selectVertexes, - currentColor, - currentIcon - } = this.props; - return ( - <> - - } - title={showTitle ? intl.get('common.vertexSets') : undefined} - trackCategory="explore" - trackAction="color_picker" - trackLabel="from_panel" - /> - - ); - } -} - -export default connect(mapState, mapDispatch)(VertexSet); diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.less b/app/assets/modules/Explore/NebulaGraph/Panel/VertexStyleSetBtn/index.less similarity index 100% rename from app/assets/modules/Explore/NebulaGraph/Panel/VertexSet/index.less rename to app/assets/modules/Explore/NebulaGraph/Panel/VertexStyleSetBtn/index.less diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/VertexStyleSetBtn/index.tsx b/app/assets/modules/Explore/NebulaGraph/Panel/VertexStyleSetBtn/index.tsx new file mode 100644 index 00000000..717e6d6b --- /dev/null +++ b/app/assets/modules/Explore/NebulaGraph/Panel/VertexStyleSetBtn/index.tsx @@ -0,0 +1,116 @@ +import * as d3 from 'd3'; +import _ from 'lodash'; +import React from 'react'; +import intl from 'react-intl-universal'; +import { connect } from 'react-redux'; + +import MenuButton from '#assets/components/Button'; +import VertexStyleSet from '#assets/components/VertexStyleSet'; +import DisplayBtn from '#assets/components/VertexStyleSet/DisplayBtn'; +import { DEFAULT_COLOR_MIX } from '#assets/config/explore'; +import { IDispatch, IRootState } from '#assets/store'; +import { INode } from '#assets/utils/interface'; + +import './index.less'; + +const mapState = (state: IRootState) => ({ + selectVertexes: state.explore.selectVertexes, +}); + +const mapDispatch = (dispatch: IDispatch) => ({ + updateVertexesStyle: selectVertexes => { + dispatch.explore.update({ + selectVertexes: [...selectVertexes], + }); + }, +}); + +interface IProps + extends ReturnType, + ReturnType { + showTitle?: boolean; + showIcon?: boolean; +} +interface IState { + visible: boolean; + color: string; + icon: string; +} +class VertexStyleSetBtn extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + visible: false, + color: DEFAULT_COLOR_MIX, + icon: '', + }; + } + + handleChangeColorComplete = color => { + const { selectVertexes, updateVertexesStyle } = this.props; + selectVertexes.forEach((vertex: INode) => (vertex.color = color)); + updateVertexesStyle(selectVertexes); + }; + + handleChangeIconComplete = icon => { + d3.selectAll('.active-node .icon').remove(); + const { selectVertexes, updateVertexesStyle } = this.props; + selectVertexes.forEach((vertex: INode) => (vertex.icon = icon.type)); + updateVertexesStyle(selectVertexes); + }; + + getUniqProperty = (list: INode[], prop: string) => { + const props = _.uniq(list.map((i: INode) => i[prop])); + if (props.length === 1) { + return props[0]; + } + return null; + }; + getStyle = (list: INode[]) => { + let color = DEFAULT_COLOR_MIX; + let icon = ''; + if (list.length > 0) { + color = this.getUniqProperty(list, 'color') || color; + icon = this.getUniqProperty(list, 'icon') || icon; + } + this.setState({ + color, + icon, + }); + }; + + componentDidUpdate() { + this.getStyle(this.props.selectVertexes); + } + + render() { + const { showTitle, selectVertexes } = this.props; + const { icon, color } = this.state; + return ( + <> + 0 ? ( + + ) : ( + + ) + } + title={showTitle ? intl.get('common.vertexSets') : undefined} + trackCategory="explore" + trackAction="color_picker" + trackLabel="from_panel" + /> + + ); + } +} + +export default connect(mapState, mapDispatch)(VertexStyleSetBtn); diff --git a/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx b/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx index 4ea626ee..b2e520c4 100644 --- a/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx +++ b/app/assets/modules/Explore/NebulaGraph/Panel/index.tsx @@ -11,7 +11,7 @@ import RollbackBtn from './Rollback'; import SearchBtn from './Search'; import UnExpandBtn from './UnExpandBtn'; import Unlock from './Unlock'; -import VertexSet from './VertexSet'; +import VertexStyleSetBtn from './VertexStyleSetBtn'; import ZoomBtn from './Zoom'; interface IProps { @@ -96,7 +96,7 @@ class Panel extends React.PureComponent { ], [ { - component: , + component: , }, { component: ( diff --git a/app/assets/modules/Import/Tasks/Upload.tsx b/app/assets/modules/Import/Tasks/Upload.tsx index 580ca5e4..fd27effa 100644 --- a/app/assets/modules/Import/Tasks/Upload.tsx +++ b/app/assets/modules/Import/Tasks/Upload.tsx @@ -1,12 +1,4 @@ -import { - Button, - Checkbox, - Icon, - Modal, - Table, - Upload, - Popconfirm, -} from 'antd'; +import { Button, Checkbox, Icon, Modal, Popconfirm, Table, Upload } from 'antd'; import _ from 'lodash'; import React from 'react'; import intl from 'react-intl-universal'; diff --git a/app/assets/store/models/d3Graph.ts b/app/assets/store/models/d3Graph.ts index 3acaa64f..563021ca 100644 --- a/app/assets/store/models/d3Graph.ts +++ b/app/assets/store/models/d3Graph.ts @@ -7,8 +7,6 @@ interface IState { showSider: boolean; showDisplayPanel: boolean; isZoom: boolean; - lastColor: string; - lastIcon: string; } export const d3Graph = createModel({ @@ -18,8 +16,6 @@ export const d3Graph = createModel({ canvasScale: 1, showSider: true, showDisplayPanel: true, - lastColor: '', - lastIcon: '', isZoom: false, }, reducers: { diff --git a/app/assets/store/models/explore.ts b/app/assets/store/models/explore.ts index fb7f6836..96e71b35 100644 --- a/app/assets/store/models/explore.ts +++ b/app/assets/store/models/explore.ts @@ -100,11 +100,8 @@ function getTagData(nodes, expand) { const { vid, tags, properties } = node; const group = getGroup(tags); const color = - expand?.vertexSets === 'custom' - ? expand.customColor - : whichColor(group); - const icon = - expand?.vertexSets === 'custom' ? expand.customIcon : ''; + expand?.vertexSets === 'custom' ? expand.customColor : whichColor(group); + const icon = expand?.vertexSets === 'custom' ? expand.customIcon : ''; const nodeProp = { tags, properties, From 078b94dc6fa17fe6f821b4aca58d51edc29f0020 Mon Sep 17 00:00:00 2001 From: Nut He <18328704+hetao92@users.noreply.github.com> Date: Fri, 8 Oct 2021 11:09:15 +0800 Subject: [PATCH 8/8] mod: update variable name --- .../components/Expand/ExpandForm/index.tsx | 22 ++++++++-------- app/assets/config/explore.ts | 2 +- app/assets/config/locale/en-US.json | 7 +++-- app/assets/config/locale/zh-CN.json | 7 +++-- .../Panel/VertexStyleSetBtn/index.tsx | 4 +-- app/assets/store/models/explore.ts | 26 +++++++++---------- 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/app/assets/components/Expand/ExpandForm/index.tsx b/app/assets/components/Expand/ExpandForm/index.tsx index fb6cd388..eb3029ec 100644 --- a/app/assets/components/Expand/ExpandForm/index.tsx +++ b/app/assets/components/Expand/ExpandForm/index.tsx @@ -155,7 +155,7 @@ class Expand extends React.Component { step, minStep, maxStep, - vertexSets, + vertexStyle, quantityLimit, } = getFieldsValue(); (this.props.asyncGetExpand({ @@ -164,7 +164,7 @@ class Expand extends React.Component { edgeTypes, edgesFields, edgeDirection, - vertexSets, + vertexStyle, quantityLimit, stepsType, step, @@ -240,7 +240,7 @@ class Expand extends React.Component { handleCustomIcon = icon => { this.setState( { - customIcon: icon.type, + customIcon: icon.content ? icon.type : '', }, this.handleUpdateRules, ); @@ -257,13 +257,13 @@ class Expand extends React.Component { step, minStep, maxStep, - vertexSets, + vertexStyle, quantityLimit, } = getFieldsValue(); this.props.updateExploreRules({ edgeTypes, edgeDirection, - vertexSets, + vertexStyle, quantityLimit, stepsType, step, @@ -441,9 +441,9 @@ class Expand extends React.Component {
)} - - {getFieldDecorator('vertexSets', { - initialValue: rules.vertexSets || 'groupByTag', + + {getFieldDecorator('vertexStyle', { + initialValue: rules.vertexStyle || 'colorGroupByTag', rules: [ { required: true, @@ -451,11 +451,11 @@ class Expand extends React.Component { ], })( - - {intl.get('explore.groupByTag')} + + {intl.get('explore.colorGroupByTag')} - {intl.get('explore.customColor')} + {intl.get('explore.customStyle')} { return ( <> 0 ? ( @@ -103,7 +103,7 @@ class VertexStyleSetBtn extends React.PureComponent { ) } - title={showTitle ? intl.get('common.vertexSets') : undefined} + title={showTitle ? intl.get('explore.vertexStyle') : undefined} trackCategory="explore" trackAction="color_picker" trackLabel="from_panel" diff --git a/app/assets/store/models/explore.ts b/app/assets/store/models/explore.ts index 96e71b35..ed3476c2 100644 --- a/app/assets/store/models/explore.ts +++ b/app/assets/store/models/explore.ts @@ -54,7 +54,7 @@ interface IExportData { interface IRules { edgeTypes?: string[]; edgeDirection?: string; - vertexSets?: string; + vertexStyle?: string; quantityLimit?: number; stepsType?: string; step?: number; @@ -100,8 +100,8 @@ function getTagData(nodes, expand) { const { vid, tags, properties } = node; const group = getGroup(tags); const color = - expand?.vertexSets === 'custom' ? expand.customColor : whichColor(group); - const icon = expand?.vertexSets === 'custom' ? expand.customIcon : ''; + expand?.vertexStyle === 'custom' ? expand.customColor : whichColor(group); + const icon = expand?.vertexStyle === 'custom' ? expand.customIcon : ''; const nodeProp = { tags, properties, @@ -211,7 +211,7 @@ export const explore = createModel({ payload: { ids: string[]; expand?: { - vertexSets: string; + vertexStyle: string; customColor; customIcon; }; @@ -325,7 +325,7 @@ export const explore = createModel({ edgesFields: any[]; edgeDirection: string; filters: any[]; - vertexSets: string; + vertexStyle: string; quantityLimit: number | null; stepsType: string; step?: string; @@ -335,11 +335,11 @@ export const explore = createModel({ customIcon?: string; }) { const data = (await this.asyncGetExpandData(payload)) as any; - const { vertexSets, customColor, customIcon } = payload; + const { vertexStyle, customColor, customIcon } = payload; await this.asyncGetExploreInfo({ data, expand: { - vertexSets, + vertexStyle, customColor, customIcon, }, @@ -363,7 +363,7 @@ export const explore = createModel({ async asyncGetExploreVertex(payload: { ids: string[]; expand?: { - vertexSets: string; + vertexStyle: string; customColor; customIcon; }; @@ -423,7 +423,7 @@ export const explore = createModel({ }, uuid: uuidv4(), }; - if (expand && expand.vertexSets === 'groupByTag') { + if (expand && expand.vertexStyle === 'colorGroupByTag') { vertex.group = 't'; } preAddVertexes.push(vertex); @@ -493,7 +493,7 @@ export const explore = createModel({ async asyncGetExploreInfo(payload: { data: IExportData; expand?: { - vertexSets: string; + vertexStyle: string; customColor; customIcon; }; @@ -529,11 +529,11 @@ export const explore = createModel({ return message.warning(intl.get('explore.missingParams')); } const data = (await this.asyncGetExpandData(rules)) as any; - const { vertexSets, customColor, customIcon } = rules; + const { vertexStyle, customColor, customIcon } = rules; await this.asyncGetExploreInfo({ data, expand: { - vertexSets, + vertexStyle, customColor, customIcon, }, @@ -547,7 +547,7 @@ export const explore = createModel({ edgesFields?: any[]; edgeDirection: string; filters?: any[]; - vertexSets?: string; + vertexStyle?: string; quantityLimit?: number | null; stepsType?: string; step?: string;