/* * << * Davinci * == * Copyright (C) 2016 - 2017 EDP * == * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * >> */ import React, { Suspense } from 'react' import Helmet from 'react-helmet' import { connect } from 'react-redux' import { createStructuredSelector } from 'reselect' import { Route } from 'react-router-dom' import { compose } from 'redux' import injectReducer from 'utils/injectReducer' import injectSaga from 'utils/injectSaga' import reducer from './reducer' import saga from './sagas' import projectReducer from '../Projects/reducer' import projectSaga from '../Projects/sagas' import vizReducer from '../Viz/reducer' import vizSaga from '../Viz/sagas' import widgetReducer from 'containers/Widget/reducer' import widgetSaga from 'containers/Widget/sagas' import viewReducer from '../View/reducer' import viewSaga from '../View/sagas' import DashboardForm from './components/DashboardForm' import DashboardAction from './components/DashboardAction' import { Button, Icon, Tooltip, Popover, Modal, Input, Tree } from 'antd' const TreeNode = Tree.TreeNode import { IconProps } from 'antd/lib/icon/index' import AntdFormType from 'antd/lib/form/Form' const Search = Input.Search import { VizActions } from 'containers/Viz/actions' import DashboardActions from './actions' import WidgetActions from 'containers/Widget/actions' import { makeSelectCurrentDashboards, makeSelectCurrentPortal, makeSelectVizLoading } from 'containers/Viz/selectors' import { makeSelectCurrentDashboard } from './selectors' import { makeSelectWidgets } from 'containers/Widget/selectors' import { hideNavigator, checkNameUniqueAction, loadDownloadList, downloadFile } from '../App/actions' import { makeSelectDownloadList, makeSelectDownloadListLoading } from '../App/selectors' import { IDownloadRecord } from '../App/types' import { DownloadTypes } from '../App/constants' import { listToTree, findFirstLeaf } from './components/localPositionUtil' import { ProjectActions } from '../Projects/actions' const { loadProjectDetail, excludeRoles } = ProjectActions import {IExludeRoles} from '../Viz/components/PortalList' const styles = require('./Dashboard.less') import {makeSelectCurrentProject, makeSelectProjectRoles} from '../Projects/selectors' import ModulePermission from '../Account/components/checkModulePermission' import { initializePermission } from '../Account/components/checkUtilPermission' import { IProject } from '../Projects/types' import EditorHeader from 'components/EditorHeader' import 'assets/less/resizer.less' const SplitPane = React.lazy(() => import('react-split-pane')) import {IProjectRoles} from '../Organizations/component/ProjectRole' import { OrganizationActions } from '../Organizations/actions' const { loadProjectRoles } = OrganizationActions import { RouteComponentWithParams } from 'utils/types' import { IWidgetFormed } from '../Widget/types' import { Grid } from './Loadable' interface IDashboardProps extends RouteComponentWithParams { modalLoading: { portal: boolean display: boolean editing: boolean dashboards: boolean slides: boolean } dashboards: IDashboard[] widgets: IWidgetFormed[] currentDashboard: IDashboard, currentProject: IProject currentPortal: any projectRoles: IProjectRoles[] downloadList: IDownloadRecord[] onLoadDashboards: (portalId: number, resolve: any) => void onLoadWidgets: (projectId: number) => void onAddDashboard: (dashboard: IDashboard, resolve: any) => any onEditDashboard: (type: string, dashboard: IDashboard[], resolve: any) => void onDeleteDashboard: (id: number, portalId: number, resolve: any) => void onHideNavigator: () => void onCheckUniqueName: (pathname: string, data: any, resolve: () => any, reject: (error: string) => any) => any onLoadPortals: (projectId) => void onLoadProjectDetail: (id) => any onExcludeRoles: (type: string, id: number, resolve?: any) => any onLoadProjectRoles: (id: number) => any onInitiateDownloadTask: (id: number, type: DownloadTypes, downloadParams?: any[]) => void onLoadDownloadList: () => void onDownloadFile: (id) => void onLoadViews: (projectId: number) => void } export interface IDashboard { id?: number name?: string config?: string parentId?: number dashboardPortalId?: number index?: number type?: number children?: any[] } interface IDashboardStates { formType: 'add' | 'edit' | 'copy' | 'move' | 'delete' | '' formVisible: boolean expandedKeys: string[], autoExpandParent: boolean searchValue: IDashboard[] dashboardData: any itemId: number dataList: any[] isExpand: boolean searchVisible: boolean isGrid: boolean checkedKeys: any[] splitSize: number portalTreeWidth: number exludeRoles: IExludeRoles[] } export class Dashboard extends React.Component { private defaultSplitSize = 190 private maxSplitSize = this.defaultSplitSize * 1.5 constructor (props) { super(props) const splitSize = +localStorage.getItem('dashboardSplitSize') || this.defaultSplitSize this.state = { formType: '', formVisible: false, expandedKeys: [], autoExpandParent: true, searchValue: [], dashboardData: [], itemId: 0, dataList: [], isExpand: true, searchVisible: false, isGrid: true, checkedKeys: [], splitSize, portalTreeWidth: 0, exludeRoles: [] } } private dashboardForm: AntdFormType = null private refHandlers = { dashboardForm: (ref) => this.dashboardForm = ref } public componentWillMount () { const { match, onLoadWidgets, onLoadPortals, onLoadProjectDetail, onLoadProjectRoles } = this.props const { projectId, portalId } = match.params onLoadWidgets(Number(projectId)) onLoadPortals(projectId) onLoadProjectDetail(projectId) onLoadProjectRoles(Number(projectId)) this.initPortal(projectId, portalId) } public componentWillReceiveProps (nextProps: IDashboardProps) { const { match, dashboards } = nextProps const { projectId, portalId } = match.params if (portalId !== this.props.match.params.portalId) { this.initPortal(projectId, portalId) } if (dashboards !== this.props.dashboards) { this.initalDashboardData(dashboards) } } public componentDidMount () { this.props.onHideNavigator() } private initPortal = (projectId, portalId) => { const { history, onLoadDashboards } = this.props const dashboardId = this.getDashboardIdFromLocation() onLoadDashboards(+portalId, (result) => { let defaultDashboardId = 0 const dashboardData = listToTree(result, 0) const treeData = { id: -1, type: 2, children: dashboardData } defaultDashboardId = findFirstLeaf(treeData) if (defaultDashboardId >= 0 && !dashboardId) { history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${defaultDashboardId}`) } this.setState({ dashboardData, isGrid: defaultDashboardId >= 0, portalTreeWidth: Number(localStorage.getItem('dashboardSplitSize')) }) this.expandAll(result) }) } private initalDashboardData = (dashboards) => { this.setState({ dashboardData: listToTree(dashboards, 0) }) this.expandAll(dashboards) } private getDashboardIdFromLocation = () => { const urlPieces = location.href.split('/') const lastModuleName = urlPieces[urlPieces.length - 2] const lastModuleId = urlPieces[urlPieces.length - 1] return lastModuleName === 'dashboard' ? Number(lastModuleId) : 0 } private changeDashboard = (dashboardId) => (e) => { const { match, history } = this.props const { projectId, portalId } = match.params const currentDashboardId = this.getDashboardIdFromLocation() if (currentDashboardId === dashboardId) { return } this.setState({ isGrid: true }, () => { history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${dashboardId}`) }) } private hideDashboardForm = () => { this.setState({ formVisible: false, checkedKeys: [] }, () => { this.dashboardForm.props.form.resetFields() }) } private onModalOk = () => { const { formType, checkedKeys } = this.state if (formType === 'delete') { const id = this.dashboardForm.props.form.getFieldValue('id') this.confirmDeleteDashboard(id) } else { this.dashboardForm.props.form.validateFieldsAndScroll((err, values) => { if (!err) { const { dashboards, match, history, onEditDashboard, onAddDashboard } = this.props const { id, name, folder, selectType, index, config } = values const dashArr = folder === '0' ? dashboards.filter((d) => d.parentId === 0) : dashboards.filter((d) => d.parentId === Number(folder)) const indexTemp = dashArr.length === 0 ? 0 : dashArr[dashArr.length - 1].index + 1 const obj = { config, dashboardPortalId: +match.params.portalId, name, // type: selectType ? 1 : 0 // todo selectType 更改位置 type: Number(selectType) } const addObj = { ...obj, parentId: Number(folder), index: indexTemp, roleIds: this.state.exludeRoles.filter((role) => !role.permission).map((p) => p.id) } const editObj = [{ ...obj, parentId: Number(folder), id, index, roleIds: this.state.exludeRoles.filter((role) => !role.permission).map((p) => p.id) }] const currentArr = dashboards.filter((d) => d.parentId === Number(folder)) const moveObj = [{ ...obj, parentId: Number(folder), id, index: currentArr.length ? currentArr[currentArr.length - 1].index + 1 : 0 }] switch (formType) { case 'add': // case 'copy': onAddDashboard(addObj, (dashboardId) => { this.hideDashboardForm() this.setState({ isGrid: true }) const { projectId, portalId } = match.params addObj.type === 0 ? history.replace(`/project/${projectId}/portal/${portalId}`) : history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${dashboardId}`) }) break case 'edit': onEditDashboard('edit', editObj, () => { this.hideDashboardForm() }) break case 'move': onEditDashboard('move', moveObj, () => { this.hideDashboardForm() }) break } } }) } } private onExpand = (expandedKeys) => { this.setState({ expandedKeys, autoExpandParent: false }) } private onDrop = (info) => { const { dashboards } = this.props const dragKey = info.dragNode.props.eventKey // 开始(要拖拽元素)id Number(dragKey) === dragNodesKeys[0] === 每一项的id // const dragPos = info.dragNode.props.pos // 开始的位置'0-1-0' const dropKey = info.node.props.eventKey // 结束的id // const dropPos = info.node.props.pos.split('-')// 结束位置的树形结构层级'0-1' // const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]) // const dragNodesKeys = info.dragNodesKeys; const loop = (data, key, callback) => { data.forEach((item, index, arr) => { if (item.id === Number(key)) { return callback(item, index, arr) } if (item.children) { return loop(item.children, key, callback) } }) } const data = [...this.state.dashboardData] let dragItem = null loop(data, dragKey, (item) => { dragItem = item }) // let dragObj loop(data, dropKey, (item, i, arr) => { const { config, dashboardPortalId, id, name, type } = dragItem let dropObj = dashboards.find((d) => d.id === Number(dropKey)) const dropObjParentId = dropObj.parentId let value = [] if (!info.dropToGap && dropObj.type === 1) { if (dragItem.type === 0) { return } } if (!info.dropToGap && dropObj.type === 0) { const currentArr = dropObj.children value = [{ config, dashboardPortalId, id, index: currentArr.length ? currentArr[currentArr.length - 1].index + 1 : 0, name, parentId: Number(dropKey), type }] this.props.onEditDashboard('move', value, (result) => { // dragObj = result }) return } let partArr = Number(dropObj.parentId) === 0 ? dashboards.filter((d) => d.parentId === 0) : (dashboards.find((d) => d.id === Number(dropObj.parentId))).children dropObj = i > info.dropPosition ? partArr[i] : partArr[i + 1] // a trick 判断拖拽位置是哪条线,如果是下面那条线则上移一位 const dropObjIndex = dropObj ? dropObj.index : partArr[i].index + 1 if (!dropObj) { value.unshift({ config, dashboardPortalId, id, index: dropObjIndex, name, parentId: dropObjParentId, type }) } if (!!dropObj && (info.dropToGap || dropObj.type === 1)) { // const dropObj = dashboards.find((d) => d.id === Number(dropKey)) partArr = Number(dropObj.parentId) === 0 ? dashboards.filter((d) => d.parentId === 0) : (dashboards.find((d) => d.id === Number(dropObj.parentId))).children const othersArr = partArr.filter((p) => p.index >= dropObj.index).filter((o) => o.id !== id) value = othersArr.map((o) => { const { config, dashboardPortalId, id, index, name, parentId, type } = o return { config, dashboardPortalId, id, index: index + 1, name, parentId, type } }) value.unshift({ config, dashboardPortalId, id, index: dropObjIndex, name, parentId: dropObj.parentId, type }) } this.props.onEditDashboard('move', value, (result) => { // dragObj = result }) }) this.setState({ dashboardData: data }) } private onAddItem = () => { this.setState({ formVisible: true, formType: 'add' }, () => { this.setState({ exludeRoles: this.props.projectRoles.map((role) => { return { ...role, permission: true } }) }) }) } private onCollapseAll = () => { this.onExpand([]) this.setState({ isExpand: false }) } private onExpandAll = () => { const { dashboards } = this.props if (dashboards) { this.expandAll(dashboards) } } private expandAll (dashboards) { const expandArr = [] dashboards.filter((d) => d.type === 0) .forEach((i) => expandArr.push(`${i.id}`)) this.onExpand(expandArr) this.setState({ isExpand: true }) } private onShowDashboardForm (item, formType) { const { dashboards, match, onLoadDashboards } = this.props this.setState({ formVisible: true, itemId: item.id }, () => { onLoadDashboards(+match.params.portalId, (result) => { setTimeout(() => { const { config, id, name, parentId, type, index } = (result as any[]).find((g) => g.id === item.id) this.dashboardForm.props.form.setFieldsValue({ id, folder: parentId ? `${(dashboards as any[]).find((g) => g.id === parentId).id}` : '0', config, name: formType === 'copy' ? `${name}_copy` : name, // selectType: type === 1, selectType: type, index }) }, 0) }) const { onExcludeRoles, projectRoles } = this.props if (onExcludeRoles && item && item.id) { onExcludeRoles('dashboard', item.id, (result: number[]) => { this.setState({ exludeRoles: projectRoles.map((role) => { return result.some((re) => re === role.id) ? {...role, permission: false} : {...role, permission: true} }) }) }) } }) } private onOperateMore = (item, type) => { if (type === 'download') { this.props.onInitiateDownloadTask(item.id, item.type === 0 ? DownloadTypes.Folder : DownloadTypes.Dashboard, []) } else { this.setState({ formType: type }, () => { this.onShowDashboardForm(item, this.state.formType) }) } } private searchDashboard = (e) => { const { dashboards } = this.props const { value } = e.target this.setState({ searchValue: value ? dashboards.filter((d) => d.name.includes(value)) : [] }) } private pickSearchDashboard = (dashboardId) => (e) => { const { dashboards } = this.props this.setState({ searchVisible: false }) const currentDashoboard = dashboards.find((d) => d.id === dashboardId) if (currentDashoboard.type === 1) { this.changeDashboard(dashboardId)(e) } else if (currentDashoboard.type === 0) { const currentFolderArr = dashboards.filter((d) => d.parentId === dashboardId) if (currentFolderArr.length !== 0) { this.changeDashboard(currentFolderArr[0].id)(e) } } } private confirmDeleteDashboard = (id) => { const { match, history, onDeleteDashboard, dashboards } = this.props const { dashboardData } = this.state const dashboardId = this.getDashboardIdFromLocation() onDeleteDashboard(id, +match.params.portalId, () => { const { projectId, portalId } = match.params const paramsDashboard = dashboards.find((d) => d.id === dashboardId) const noCurrentDashboards = dashboardData.filter((d) => d.id !== id) if (noCurrentDashboards.length !== 0 && paramsDashboard) { const remainDashboards = noCurrentDashboards.filter((r) => r.parentId !== id) const treeData = { id: -1, type: 2, children: remainDashboards } if (dashboardId === id || paramsDashboard.parentId === id) { const defaultDashboardId = findFirstLeaf(treeData) history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${defaultDashboardId}`) } } else { history.replace(`/project/${projectId}/portal/${portalId}/dashboard/-1`) this.setState({ isGrid: false }) } this.hideDashboardForm() }) } private searchVisibleChange = (visible) => { this.setState({ searchVisible: visible }) } private handleTree = (clickKey, obj) => { const { expandedKeys } = this.state this.setState({ autoExpandParent: false }) if (obj.selected) { if (expandedKeys.indexOf(clickKey[0]) < 0) { expandedKeys.push(clickKey[0]) this.setState({ expandedKeys }) } else { this.setState({ expandedKeys: expandedKeys.filter((e) => e !== clickKey[0]) }) } } else { let currentKey = [] if (expandedKeys.length === 0) { expandedKeys.push(obj.node.props.eventKey) currentKey = expandedKeys } else { currentKey = expandedKeys.filter((e) => e !== obj.node.props.title) } this.setState({ expandedKeys: currentKey }) } } private cancel = () => { const { history, match } = this.props const prefix = window.localStorage.getItem('inDataService') ?? '' const prefixPath = prefix ? '/' + prefix : prefix history.replace(`/project/${match.params.projectId}${prefixPath}/vizs`) } private initCheckNodes = (checkedKeys) => { this.setState({ checkedKeys }) } private changePortalTreeWidth = (newSize: number) => { this.setState({ portalTreeWidth: newSize }) } private saveSplitSize = (newSize: number) => { localStorage.setItem('dashboardSplitSize', newSize.toString()) this.changePortalTreeWidth(newSize) // triggering ResponsiveReactGridLayout readjustment const resizeEvent = document.createEvent('Event') resizeEvent.initEvent('resize', false, true) window.dispatchEvent(resizeEvent) } private changePermission = (scope: IExludeRoles, event) => { scope.permission = event.target.checked this.setState({ exludeRoles: this.state.exludeRoles.map((role) => role && role.id === scope.id ? scope : role) }) } public render () { const { match, currentDashboard, dashboards, widgets, modalLoading, currentProject, currentPortal, downloadList, onCheckUniqueName, onLoadDownloadList, onDownloadFile } = this.props const { formType, formVisible, searchValue, dashboardData, isGrid, searchVisible, checkedKeys, splitSize, portalTreeWidth } = this.state const items = searchValue.map((s) => { return
  • {s.name}
  • }) const dashboardId = this.getDashboardIdFromLocation().toString() let modalTitle = '' switch (formType) { case 'add': modalTitle = '新增' break case 'edit': modalTitle = '修改' break case 'copy': modalTitle = '复制' break case 'move': modalTitle = '移动' break case 'delete': modalTitle = '提示' break } const modalButtons = [( ), ( )] const loop = (data, depth = 0) => data.map((item) => { const dashboardAction = ( ) if (item.type === 0) { return ( } key={item.id} title={dashboardAction} > {loop(item.children, depth + 1)} ) } return } key={item.id} title={dashboardAction} /> }) const AdminIcon = ModulePermission(currentProject, 'viz', true)(Icon) const portalName = currentPortal && currentPortal.name const portalDesc = currentPortal && currentPortal.description return (
      {items}
    } trigger="click" visible={searchVisible} onVisibleChange={this.searchVisibleChange} >
  • 收起全部
  • 展开全部
  • } trigger="click" >
    { dashboardData.length ?
    {loop(dashboardData)}
    : isGrid ?

    Loading tree......

    : '' }
    { isGrid && widgets ? : (

    请创建文件夹或 Dashboard

    ) }
    ) } } const mapStateToProps = createStructuredSelector({ dashboards: makeSelectCurrentDashboards(), widgets: makeSelectWidgets(), currentDashboard: makeSelectCurrentDashboard(), modalLoading: makeSelectVizLoading(), currentProject: makeSelectCurrentProject(), currentPortal: makeSelectCurrentPortal(), projectRoles: makeSelectProjectRoles(), downloadList: makeSelectDownloadList() }) export function mapDispatchToProps (dispatch) { return { onLoadDashboards: (portalId, resolve) => dispatch(VizActions.loadPortalDashboards(portalId, resolve, false)), onLoadWidgets: (projectId: number) => dispatch(WidgetActions.loadWidgets(projectId)), onAddDashboard: (dashboard, resolve) => dispatch(VizActions.addDashboard(dashboard, resolve)), onEditDashboard: (formType, dashboard, resolve) => dispatch(VizActions.editDashboard(formType, dashboard, resolve)), onDeleteDashboard: (id, portalId, resolve) => dispatch(VizActions.deleteDashboard(id, portalId, resolve)), onHideNavigator: () => dispatch(hideNavigator()), onCheckUniqueName: (pathname, data, resolve, reject) => dispatch(checkNameUniqueAction(pathname, data, resolve, reject)), onLoadPortals: (projectId) => dispatch(VizActions.loadPortals(projectId)), onLoadProjectDetail: (id) => dispatch(loadProjectDetail(id)), onExcludeRoles: (type, id, resolve) => dispatch(excludeRoles(type, id, resolve)), onLoadProjectRoles: (id) => dispatch(loadProjectRoles(id)), onInitiateDownloadTask: (id, type, downloadParams?) => dispatch(DashboardActions.initiateDownloadTask(id, type, downloadParams)), onLoadDownloadList: () => dispatch(loadDownloadList()), onDownloadFile: (id) => dispatch(downloadFile(id)) } } const withConnect = connect(mapStateToProps, mapDispatchToProps) const withReducer = injectReducer({ key: 'dashboard', reducer }) const withSaga = injectSaga({ key: 'dashboard', saga }) const withProjectReducer = injectReducer({ key: 'project', reducer: projectReducer }) const withProjectSaga = injectSaga({ key: 'project', saga: projectSaga }) const withVizReducer = injectReducer({ key: 'viz', reducer: vizReducer }) const withVizSaga = injectSaga({ key: 'viz', saga: vizSaga }) const withWidgetReducer = injectReducer({ key: 'widget', reducer: widgetReducer }) const withWidgetSaga = injectSaga({ key: 'widget', saga: widgetSaga }) const withViewReducer = injectReducer({ key: 'view', reducer: viewReducer }) const withViewSaga = injectSaga({ key: 'view', saga: viewSaga }) export default compose( withReducer, withProjectReducer, withVizReducer, withWidgetReducer, withViewReducer, withSaga, withProjectSaga, withVizSaga, withWidgetSaga, withViewSaga, withConnect )(Dashboard)