diff options
Diffstat (limited to 'workflow/workflow-designer-ui/src/main/frontend/src/shared')
31 files changed, 1707 insertions, 0 deletions
diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js new file mode 100644 index 00000000..39400fd9 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/Description/index.js @@ -0,0 +1,44 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 from 'react'; +import PropTypes from 'prop-types'; +import { I18n } from 'react-redux-i18n'; + +const Description = ({ description, onDataChange, dataTestId }) => ( + <div className="description-part"> + <div className="sdc-input"> + <div className="sdc-input__label"> + {I18n.t('workflow.general.description')} + </div> + <textarea + value={description} + data-test-id={dataTestId || 'description'} + onChange={event => { + onDataChange({ description: event.target.value }); + }} + className="field-section sdc-input__input" + /> + </div> + </div> +); + +Description.propTypes = { + description: PropTypes.string, + onDataChange: PropTypes.func, + dataTestId: PropTypes.string +}; + +export default Description; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js new file mode 100644 index 00000000..dcf11f2f --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/Select/index.js @@ -0,0 +1,54 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 from 'react'; +import PropTypes from 'prop-types'; +import { isEmpty } from 'lodash'; + +const Select = props => { + const { dataObj, selectedItemValue, disabled, label } = props; + return ( + <div className="select-wrapper version-selector sdc-input"> + {label && <label>{label}</label>} + <select + className="inputinput-selector" + key={'selector'} + value={selectedItemValue} + disabled={disabled} + data-test-id="vc-select-box"> + {!isEmpty(dataObj) && + dataObj.map(item => { + return ( + <option + key={'select-item' + item.id} + value={item.id} + data-test-id="vc-option"> + {item.displayed || item.name} + </option> + ); + })} + </select> + </div> + ); +}; + +Select.propTypes = { + dataObj: PropTypes.arrayOf(Object), + selectedItemValue: PropTypes.string, + disabled: PropTypes.bool, + label: PropTypes.string +}; + +export default Select; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js new file mode 100644 index 00000000..e89d9aa1 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/components/VersionInfo/index.js @@ -0,0 +1,59 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 from 'react'; +import PropTypes from 'prop-types'; +import { I18n } from 'react-redux-i18n'; +import { Localize } from 'react-redux-i18n'; +export const LabeledValue = ({ title, value }) => ( + <React.Fragment> + <div className="label">{title}</div> + <div className="value">{value}</div> + </React.Fragment> +); + +LabeledValue.propTypes = { + title: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]) +}; + +export const VersionInfo = ({ created, modified, children }) => ( + <div className="version-info-part"> + {created && ( + <LabeledValue + title={I18n.t('workflow.general.created')} + value={<Localize value={created} dateFormat="date.short" />} + /> + )} + {modified && ( + <LabeledValue + title={I18n.t('workflow.general.modified')} + value={<Localize value={modified} dateFormat="date.short" />} + /> + )} + {children} + </div> +); + +VersionInfo.defaultProps = { + created: '', + modified: '' +}; + +VersionInfo.propTypes = { + created: PropTypes.string, + modified: PropTypes.string, + children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) +}; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js new file mode 100644 index 00000000..960e255d --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/errorResponseHandler/errorResponseHandler.js @@ -0,0 +1,32 @@ +/* + * Copyright © 2018 European Support Limited + * + * 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 from 'react'; +import store from 'wfapp/store'; +import { showErrorModalAction } from 'shared/modal/modalWrapperActions'; + +export default error => { + const errorData = { + title: error.statusText, + body: error.responseText ? error.responseText : 'GENERIC ERROR' + }; + store.dispatch( + showErrorModalAction({ + ...errorData, + withButtons: true, + closeButtonText: 'Ok' + }) + ); +}; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx new file mode 100644 index 00000000..0b4c9f42 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/Loader.jsx @@ -0,0 +1,50 @@ +/* + * Copyright © 2016-2017 European Support Limited + * + * 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 from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +export const mapStateToProps = ({ loader: { isLoading } }) => { + return { + isLoading + }; +}; + +export class Loader extends React.Component { + shouldComponentUpdate(nextProps) { + return nextProps.isLoading !== this.props.isLoading; + } + + render() { + let { isLoading } = this.props; + return ( + <div className="onboarding-loader"> + {isLoading && ( + <div className="onboarding-loader-backdrop"> + <div className="tlv-loader large" /> + </div> + )} + </div> + ); + } +} + +Loader.propTypes = { + isLoading: PropTypes.bool +}; + +export default connect(mapStateToProps)(Loader); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js new file mode 100644 index 00000000..02ce84ca --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderConstants.js @@ -0,0 +1,38 @@ +/* + * Copyright © 2016-2017 European Support Limited + * + * 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. + */ +export const actionTypes = { + SHOW: 'SHOW', + HIDE: 'HIDE', + + SEND_REQUEST: 'loader/SEND_REQUEST', + RECEIVE_RESPONSE: 'loader/RECEIVE_RESPONSE' +}; + +export const initialState = { + fetchingRequests: 0, + currentlyFetching: [], + isLoading: false +}; + +export const sendRequestAction = url => ({ + type: actionTypes.SEND_REQUEST, + url +}); + +export const recieveResponseAction = url => ({ + type: actionTypes.RECEIVE_RESPONSE, + url +}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js new file mode 100644 index 00000000..d883492e --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/LoaderReducer.js @@ -0,0 +1,49 @@ +/* + * Copyright © 2016-2017 European Support Limited + * + * 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 { actionTypes, initialState } from 'shared/loader/LoaderConstants.js'; + +export default (state = initialState, action) => { + let fetchingRequests = state.fetchingRequests; + let newArray; + switch (action.type) { + case actionTypes.SEND_REQUEST: + fetchingRequests++; + newArray = state.currentlyFetching.slice(); + newArray.splice(0, 0, action.url); + return { + fetchingRequests: fetchingRequests, + currentlyFetching: newArray, + isLoading: true + }; + case actionTypes.RECEIVE_RESPONSE: + fetchingRequests--; + + newArray = state.currentlyFetching.filter(item => { + return item !== action.url; + }); + return { + currentlyFetching: newArray, + fetchingRequests: fetchingRequests, + isLoading: fetchingRequests !== 0 + }; + case actionTypes.SHOW: + return { isLoading: true }; + case actionTypes.HIDE: + return { isLoading: false }; + default: + return state; + } +}; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js new file mode 100644 index 00000000..3ae40361 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/loader/__tests__/loaderReducer-test.js @@ -0,0 +1,56 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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. +*/ + +'use strict'; + +import loaderReducer from 'shared/loader/LoaderReducer'; +import { + initialState, + sendRequestAction, + recieveResponseAction +} from 'shared/loader/LoaderConstants'; + +describe('Loader reducer', () => { + it('return initial state', () => { + expect(loaderReducer(undefined, {})).toEqual(initialState); + }); + + it('send request action test', () => { + const url = 'TEST'; + const loaderData = { + fetchingRequests: 1, + currentlyFetching: [url], + isLoading: true + }; + + expect(loaderReducer(initialState, sendRequestAction(url))).toEqual( + loaderData + ); + }); + + it('recieve response action test', () => { + const url = 'TEST'; + const loaderData = { + fetchingRequests: 1, + currentlyFetching: [url], + isLoading: true + }; + + expect(loaderReducer(loaderData, recieveResponseAction(url))).toEqual( + initialState + ); + }); +}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js new file mode 100644 index 00000000..71bda2be --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapper.js @@ -0,0 +1,34 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { connect } from 'react-redux'; + +import ModalWrapperView from 'shared/modal/ModalWrapperView'; +import { hideModalAction } from 'shared/modal/modalWrapperActions'; + +const mapStateToProps = state => ({ + modal: state.modal +}); + +const mapDispatchToProps = dispatch => ({ + hideModal: () => dispatch(hideModalAction()) +}); + +const ModalWrapper = connect(mapStateToProps, mapDispatchToProps)( + ModalWrapperView +); + +export default ModalWrapper; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx new file mode 100644 index 00000000..95be8cda --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/ModalWrapperView.jsx @@ -0,0 +1,99 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 from 'react'; +import PropTypes from 'prop-types'; + +import { + Modal, + ModalHeader, + ModalTitle, + ModalBody, + ModalFooter +} from 'sdc-ui/lib/react'; + +import modalWrapperComponents from 'shared/modal/modalWrapperComponents'; + +class ModalWrapperView extends React.Component { + constructor(props) { + super(props); + + this.handleClose = this.handleClose.bind(this); + } + + handleClose() { + const { hideModal, onClose } = this.props; + + hideModal(); + + if (typeof onClose === 'function') { + onClose(); + } + } + + render() { + const { modal } = this.props; + + if (!modal || !modal.type) { + return null; + } + + const { + size, + type, + title, + body, + withButtons, + actionButtonText, + actionButtonClick, + closeButtonText, + customComponentName, + customComponentProps + } = modal; + + const CustomComponent = + customComponentName && modalWrapperComponents[customComponentName]; + + return ( + <Modal show size={size} type={type}> + <ModalHeader onClose={this.handleClose} type={type}> + {title && <ModalTitle>{title}</ModalTitle>} + </ModalHeader> + {body && <ModalBody>{body}</ModalBody>} + {CustomComponent && ( + <CustomComponent {...customComponentProps} /> + )} + {withButtons && ( + <ModalFooter + actionButtonText={actionButtonText} + actionButtonClick={actionButtonClick} + closeButtonText={closeButtonText} + onClose={this.handleClose} + withButtons + /> + )} + </Modal> + ); + } +} + +ModalWrapperView.propTypes = { + hideModal: PropTypes.func, + onClose: PropTypes.func, + modal: PropTypes.object +}; + +export default ModalWrapperView; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js new file mode 100644 index 00000000..0be53de4 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperActions-test.js @@ -0,0 +1,85 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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. +*/ + +'use strict'; + +import { + SHOW_MODAL, + HIDE_MODAL, + showInfoModalAction, + showAlertModalAction, + showErrorModalAction, + showCustomModalAction, + hideModalAction +} from 'shared/modal/modalWrapperActions'; + +describe('Modal Wrapper Actions', () => { + const actions = [ + { fn: showInfoModalAction, type: 'info', size: 'small' }, + { fn: showAlertModalAction, type: 'alert', size: 'small' }, + { fn: showErrorModalAction, type: 'error', size: 'small' }, + { fn: showCustomModalAction, type: 'custom', size: 'medium' } + ]; + + it('returns correct action', () => { + actions.forEach(action => { + const payload = { + title: 'Title', + body: 'Body', + withButtons: true, + actionButtonText: 'Action Button Text', + actionButtonClick: () => {}, + closeButtonText: 'Close Button Text' + }; + + const expected = { + type: SHOW_MODAL, + payload: { + size: action.size, + ...payload, + type: action.type + } + }; + + expect(action.fn(payload)).toEqual(expected); + }); + }); + + it('returns correct size in action', () => { + actions.forEach(action => { + const size = 'large'; + + const payload = { + size + }; + + const expected = { + type: SHOW_MODAL, + payload: { size, type: action.type } + }; + + expect(action.fn(payload)).toEqual(expected); + }); + }); + + it('returns hide modal action', () => { + const expected = { + type: HIDE_MODAL + }; + + expect(hideModalAction()).toEqual(expected); + }); +}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js new file mode 100644 index 00000000..c33190c5 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/__tests__/modalWrapperReducer-test.js @@ -0,0 +1,61 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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. +*/ + +'use strict'; + +import modalWrapperReducer from 'shared/modal/modalWrapperReducer'; +import { + showInfoModalAction, + hideModalAction +} from 'shared/modal/modalWrapperActions'; + +describe('Modal Wrapper Reducer', () => { + it('returns the initial state', () => { + const state = { + type: 'info', + title: 'Title' + }; + expect(modalWrapperReducer(state, {})).toEqual(state); + }); + + it('returns correct state for show modal action', () => { + const payload = { + size: 'medium', + title: 'Title' + }; + + const action = showInfoModalAction(payload); + + const expected = { + ...payload, + type: 'info' + }; + + expect(modalWrapperReducer({}, action)).toEqual(expected); + }); + + it('returns correct state for hide modal action', () => { + const state = { + size: 'medium', + title: 'Title', + type: 'info' + }; + + const action = hideModalAction(); + + expect(modalWrapperReducer(state, action)).toEqual({}); + }); +}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js new file mode 100644 index 00000000..fe0ca0a5 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperActions.js @@ -0,0 +1,46 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { createAction } from 'redux-actions'; + +export const SHOW_MODAL = 'modal/SHOW_MODAL'; +export const HIDE_MODAL = 'modal/HIDE_MODAL'; + +export const showInfoModalAction = createAction(SHOW_MODAL, payload => ({ + size: 'small', + ...payload, + type: 'info' +})); + +export const showAlertModalAction = createAction(SHOW_MODAL, payload => ({ + size: 'small', + ...payload, + type: 'alert' +})); + +export const showErrorModalAction = createAction(SHOW_MODAL, payload => ({ + size: 'small', + ...payload, + type: 'error' +})); + +export const showCustomModalAction = createAction(SHOW_MODAL, payload => ({ + size: 'medium', + ...payload, + type: 'custom' +})); + +export const hideModalAction = createAction(HIDE_MODAL); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js new file mode 100644 index 00000000..1e051b2f --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperComponents.js @@ -0,0 +1,27 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 NewWorkflow from 'features/workflow/create/CreateWorkflow'; +import NewVersion from 'features/version/create/CreateVersion'; + +export const NEW_WORKFLOW_MODAL = 'NEW_WORKFLOW_MODAL'; +export const NEW_VERSION_MODAL = 'NEW_VERSION_MODAL'; + +const modalWrapperComponents = { + NEW_WORKFLOW_MODAL: NewWorkflow, + NEW_VERSION_MODAL: NewVersion +}; + +export default modalWrapperComponents; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js new file mode 100644 index 00000000..600998b6 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/modal/modalWrapperReducer.js @@ -0,0 +1,32 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { SHOW_MODAL, HIDE_MODAL } from 'shared/modal/modalWrapperActions'; + +const modalWrapperReducer = (state = {}, action) => { + const { type, payload } = action; + + switch (type) { + case SHOW_MODAL: + return { ...payload }; + case HIDE_MODAL: + return {}; + default: + return state; + } +}; + +export default modalWrapperReducer; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx new file mode 100644 index 00000000..6688cb55 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationLink.jsx @@ -0,0 +1,48 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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 from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +function getItemDataTestId(itemId) { + return itemId.split('|')[0]; +} + +const NavigationLink = ({ item, activeItemId, onClick }) => { + return ( + <div + key={'navAction_' + item.id} + className={classnames('navigation-group-item-name', { + selected: item.id === activeItemId, + disabled: item.disabled, + 'bold-name': item.expanded, + hidden: item.hidden + })} + onClick={event => onClick(event, item)} + data-test-id={'navbar-group-item-' + getItemDataTestId(item.id)}> + {item.name} + </div> + ); +}; + +NavigationLink.propTypes = { + item: PropTypes.object, + activeItemId: PropTypes.string, + onClick: PropTypes.func +}; + +export default NavigationLink; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx new file mode 100644 index 00000000..f8a7d46f --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenu.jsx @@ -0,0 +1,39 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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 from 'react'; +import PropTypes from 'prop-types'; +import NavigationMenuItems from './NavigationMenuItems'; + +const NavigationMenu = ({ menu, activeItemId, onNavigationItemClick }) => { + return ( + <div className="navigation-group" key={menu.id}> + <NavigationMenuItems + items={menu.items} + activeItemId={activeItemId} + onNavigationItemClick={onNavigationItemClick} + /> + </div> + ); +}; + +NavigationMenu.propTypes = { + menu: PropTypes.object, + activeItemId: PropTypes.string.isRequired, + onNavigationItemClick: PropTypes.func +}; + +export default NavigationMenu; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx new file mode 100644 index 00000000..540e09c7 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItem.jsx @@ -0,0 +1,44 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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 from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import NavigationLink from './NavigationLink'; + +const NavigationMenuItem = ({ onNavigationItemClick, item, activeItemId }) => { + return ( + <div + className={classnames('navigation-group-item', { + 'selected-item': item.id === activeItemId + })} + key={'item_' + item.id}> + <NavigationLink + item={item} + activeItemId={activeItemId} + onClick={onNavigationItemClick} + /> + </div> + ); +}; + +NavigationMenuItem.propTypes = { + onNavigationItemClick: PropTypes.func, + item: PropTypes.object, + activeItemId: PropTypes.string +}; + +export default NavigationMenuItem; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx new file mode 100644 index 00000000..56bad084 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/NavigationMenuItems.jsx @@ -0,0 +1,47 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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 from 'react'; +import PropTypes from 'prop-types'; +import NavigationMenuItem from './NavigationMenuItem'; + +const NavigationMenuItems = ({ + items, + activeItemId, + onNavigationItemClick +}) => { + return ( + <div className="navigation-group-items"> + {items && + items.map(item => ( + <NavigationMenuItem + key={'menuItem_' + item.id} + item={item} + activeItemId={activeItemId} + onNavigationItemClick={onNavigationItemClick} + /> + ))} + </div> + ); +}; + +NavigationMenuItems.propTypes = { + items: PropTypes.array, + activeItemId: PropTypes.string, + onNavigationItemClick: PropTypes.func +}; + +export default NavigationMenuItems; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js new file mode 100644 index 00000000..b3af6f47 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/navigationSideBar/index.js @@ -0,0 +1,73 @@ +/*! + * Copyright © 2016-2018 European Support Limited + * + * 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import NavigationMenu from './NavigationMenu'; + +class NavigationSideBar extends Component { + static propTypes = { + activeItemId: PropTypes.string, + onSelect: PropTypes.func, + onToggle: PropTypes.func, + groups: PropTypes.array, + disabled: PropTypes.bool, + onNavigationItemClick: PropTypes.func + }; + + constructor(props) { + super(props); + this.state = { + activeItemId: null + }; + this.handleItemClicked = this.handleItemClicked.bind(this); + } + + render() { + let { groups, activeItemId, disabled = false } = this.props; + + return ( + <div + className={`navigation-side-content ${ + disabled ? 'disabled' : '' + }`}> + {groups.map(group => ( + <NavigationMenu + menu={group} + activeItemId={activeItemId} + onNavigationItemClick={this.handleItemClicked} + key={'menu_' + group.id} + /> + ))} + </div> + ); + } + + handleItemClicked(event, item) { + event.stopPropagation(); + if (this.props.onToggle) { + this.props.onToggle(this.props.groups, item.id); + } + if (item.onSelect) { + item.onSelect(); + } + if (this.props.onSelect) { + this.props.onSelect(item); + } + } +} + +export default NavigationSideBar; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js new file mode 100644 index 00000000..6d9f7242 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/Notifications.js @@ -0,0 +1,33 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { connect } from 'react-redux'; +import NotificationsView from 'shared/notifications/NotificationsView'; +import { notificationActions } from 'shared/notifications/notificationsActions'; + +const mapStateToProps = state => { + return { + notifications: state.notifications + }; +}; + +function mapDispatchToProps(dispatch) { + return { + removeNotifications: payload => + dispatch(notificationActions.removeNotification(payload)) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(NotificationsView); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx new file mode 100644 index 00000000..710ffce7 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/NotificationsView.jsx @@ -0,0 +1,56 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Notification } from 'sdc-ui/lib/react/'; +import { CSSTransition, TransitionGroup } from 'react-transition-group'; + +export default class NotificationsView extends PureComponent { + static propTypes = { + removeNotifications: PropTypes.func, + notifications: PropTypes.array + }; + + constructor(props) { + super(props); + } + + render() { + const { notifications, removeNotifications } = this.props; + return ( + <div className="workflow-notifications-container position-top-right"> + <TransitionGroup> + {notifications.map(item => ( + <CSSTransition + in={true} + timeout={500} + unmountOnExit + classNames="react-transition" + key={`notification-transition-${item.id}`}> + <Notification + key={item.id} + type={item.type} + title={item.title} + message={item.message} + onClick={() => removeNotifications(item)} + /> + </CSSTransition> + ))} + </TransitionGroup> + </div> + ); + } +} diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js new file mode 100644 index 00000000..616f10ff --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/NotificationView_snapshot-test.js @@ -0,0 +1,30 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 from 'react'; +import renderer from 'react-test-renderer'; + +import NotificationsView from 'shared/notifications/NotificationsView'; + +describe('Notification View Snapshot', () => { + it('renders correctly', () => { + const tree = renderer + .create(<NotificationsView notifications={[]} />) + .toJSON(); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap new file mode 100644 index 00000000..eb4a573f --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/__snapshots__/NotificationView_snapshot-test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Notification View Snapshot renders correctly 1`] = ` +<div + className="workflow-notifications-container position-top-right" +> + <div /> +</div> +`; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js new file mode 100644 index 00000000..d308d22d --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/__tests__/notificationsReducer-test.js @@ -0,0 +1,61 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 notificationsReducer from 'shared/notifications/notificationsReducer'; +import { actionTypes } from 'shared/notifications/notificationsConstants'; + +describe('Notifications Reducer', () => { + it('Returns empty state array', () => { + expect(notificationsReducer(undefined, {})).toEqual([]); + }); + it('Add notification obj to the notification arr in store', () => { + const action = { + type: actionTypes.ADD_NOTIFICATION, + payload: { + title: 'Notification1', + message: 'This is a test', + type: 'info', + id: '4a2b0038-e442-4ccb-b577-5d04eee6fdfb', + timeout: 200 + } + }; + expect(notificationsReducer([], action)).toEqual([action.payload]); + }); + it('Remove notification instance from store', () => { + const resultState = [ + { + title: 'Notification1', + message: 'This is a test', + type: 'info', + id: '4a2b0038-e442-4ccb-b577-5d04eee6fdfb', + timeout: 200 + } + ]; + const removeAction = { + type: actionTypes.REMOVE_NOTIFICATION, + payload: { + title: 'Notification1', + message: 'This is a test', + type: 'info', + id: '4a2b0038-e442-4ccb-b577-5d04eee6fdfb', + timeout: 200 + } + }; + expect(notificationsReducer([], removeAction)).toEqual([]); + expect(notificationsReducer(resultState, removeAction)).toEqual( + resultState.filter(item => item.id !== removeAction.payload.id) + ); + }); +}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js new file mode 100644 index 00000000..84f9da83 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsActions.js @@ -0,0 +1,60 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { actionTypes } from 'shared/notifications/notificationsConstants'; +import UUID from 'uuid-js'; + +export const notificationActions = { + showNotification: item => ({ + type: actionTypes.ADD_NOTIFICATION, + payload: { + ...item, + id: UUID.create().toString() + } + }), + + showSuccess: ({ title, message, timeout }) => + notificationActions.showNotification({ + title, + message, + timeout, + type: 'success' + }), + showInfo: ({ title, message, timeout }) => + notificationActions.showNotification({ + title, + message, + timeout, + type: 'info' + }), + showWarning: ({ title, message, timeout }) => + notificationActions.showNotification({ + title, + message, + timeout, + type: 'warning' + }), + showError: ({ title, message, timeout }) => + notificationActions.showNotification({ + title, + message, + timeout, + type: 'error' + }), + removeNotification: item => ({ + type: actionTypes.REMOVE_NOTIFICATION, + payload: item + }) +}; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js new file mode 100644 index 00000000..3e166424 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsConstants.js @@ -0,0 +1,21 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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. +*/ + +export const actionTypes = { + ADD_NOTIFICATION: 'notification/ADD_NOTIFICATION', + REMOVE_NOTIFICATION: 'notification/REMOVE_NOTIFICATION' +}; +export const notificationTimeout = 4000; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js new file mode 100644 index 00000000..f0989cf5 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsReducer.js @@ -0,0 +1,30 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { actionTypes } from 'shared/notifications/notificationsConstants'; + +function notificationsReducer(state = [], action) { + switch (action.type) { + case actionTypes.ADD_NOTIFICATION: + return [...state, action.payload]; + case actionTypes.REMOVE_NOTIFICATION: + return state.filter(item => item.id !== action.payload.id); + default: + return state; + } +} + +export default notificationsReducer; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js new file mode 100644 index 00000000..c30930a2 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/notifications/notificationsSagas.js @@ -0,0 +1,33 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 { takeEvery, put } from 'redux-saga/effects'; +import { delay } from 'redux-saga'; +import { + actionTypes, + notificationTimeout +} from 'shared/notifications/notificationsConstants.js'; +import { notificationActions } from 'shared/notifications/notificationsActions'; + +export function* handleComingNotifications(params) { + const { timeout, ...data } = params; + const interval = timeout || notificationTimeout; + yield delay(interval); + yield put(notificationActions.removeNotification(data.payload)); +} + +export function* watchNotifications() { + yield takeEvery(actionTypes.ADD_NOTIFICATION, handleComingNotifications); +} diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx new file mode 100644 index 00000000..e0046926 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/searchInput/SearchInput.jsx @@ -0,0 +1,123 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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 from 'react'; +import PropTypes from 'prop-types'; +import SVGIcon from 'sdc-ui/lib/react/SVGIcon'; + +class ExpandableInput extends React.Component { + static propTypes = { + onChange: PropTypes.func, + value: PropTypes.string + }; + + state = { showInput: false }; + + handleRef = input => { + this.domNode = input; + + this.domNode && this.domNode.focus(); + }; + + showInput = () => { + this.setState({ showInput: true }); + }; + + hideInput = () => { + this.setState({ showInput: false }); + }; + + closeInput = () => { + if (!this.props.value) { + this.hideInput(); + } + }; + + getValue = () => { + return this.props.value; + }; + + handleChange = e => { + this.props.onChange(e.target.value); + }; + + handleClose() { + this.props.onChange(''); + + this.hideInput(); + } + + handleKeyDown = e => { + if (e.key === 'Escape') { + e.preventDefault(); + + this.handleClose(); + } + }; + + handleBlur = () => { + if (!this.props.value) { + this.setState({ showInput: false }); + } + }; + + render() { + let { value } = this.props; + + const { showInput } = this.state; + + if (!showInput) { + return ( + <div className="search-input-top"> + <SVGIcon + className="search-input-wrapper closed" + name="search" + onClick={this.showInput} + /> + </div> + ); + } + + return ( + <div className="search-input-top"> + <div className="search-input-wrapper opened"> + <div className="search-input-control"> + <input + type="text" + value={value} + ref={this.handleRef} + className="input-control" + onChange={this.handleChange} + onKeyDown={this.handleKeyDown} + onBlur={this.handleBlur} + /> + </div> + {value && ( + <SVGIcon + onClick={() => this.handleClose()} + name="close" + /> + )} + {!value && ( + <SVGIcon name="search" onClick={this.handleBlur} /> + )} + </div> + </div> + ); + } +} + +export default ExpandableInput; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx new file mode 100644 index 00000000..7b25ab89 --- /dev/null +++ b/workflow/workflow-designer-ui/src/main/frontend/src/shared/tree/Tree.jsx @@ -0,0 +1,234 @@ +/* +* Copyright © 2018 European Support Limited +* +* 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { select } from 'd3-selection'; +import { tree, stratify } from 'd3-hierarchy'; +function diagonal(d) { + const offset = 50; + return ( + 'M' + + d.y + + ',' + + d.x + + 'C' + + (d.parent.y + offset) + + ',' + + d.x + + ' ' + + (d.parent.y + offset) + + ',' + + d.parent.x + + ' ' + + d.parent.y + + ',' + + d.parent.x + ); +} + +const nodeRadius = 8; +const verticalSpaceBetweenNodes = 70; +const NARROW_HORIZONTAL_SPACES = 47; +const WIDE_HORIZONTAL_SPACES = 65; + +const stratifyFn = stratify() + .id(d => d.id) + .parentId(d => d.parent); + +class Tree extends Component { + static propTypes = { + name: PropTypes.string, + width: PropTypes.number, + allowScaleWidth: PropTypes.bool, + nodes: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + parent: PropTypes.string + }) + ), + selectedNodeId: PropTypes.string, + onNodeClick: PropTypes.func, + onRenderedBeyondWidth: PropTypes.func, + scrollable: PropTypes.bool, + toWiden: PropTypes.bool + }; + + static defaultProps = { + width: 500, + allowScaleWidth: true, + name: 'default-name' + }; + + render() { + let { width, name, scrollable = false } = this.props; + return ( + <div + className={`tree-view ${name}-container ${ + scrollable ? 'scrollable' : '' + }`}> + <svg width={width} className={name} /> + </div> + ); + } + + componentDidMount() { + this.renderTree(); + } + + componentDidUpdate(prevProps) { + if ( + this.props.nodes.length !== prevProps.nodes.length || + this.props.selectedNodeId !== prevProps.selectedNodeId + ) { + this.renderTree(); + } + } + + renderTree() { + let { + width, + nodes, + name, + allowScaleWidth, + selectedNodeId, + onRenderedBeyondWidth, + toWiden + } = this.props; + if (nodes.length > 0) { + let horizontalSpaceBetweenLeaves = toWiden + ? WIDE_HORIZONTAL_SPACES + : NARROW_HORIZONTAL_SPACES; + const treeFn = tree().nodeSize([ + horizontalSpaceBetweenLeaves, + verticalSpaceBetweenNodes + ]); //.size([width - 50, height - 50]) + let root = stratifyFn(nodes).sort((a, b) => + a.data.name.localeCompare(b.data.name) + ); + let svgHeight = + verticalSpaceBetweenNodes * root.height + nodeRadius * 6; + + treeFn(root); + + let nodesXValue = root.descendants().map(node => node.x); + let maxX = Math.max(...nodesXValue); + let minX = Math.min(...nodesXValue); + + let svgTempWidth = + (maxX - minX) / 30 * horizontalSpaceBetweenLeaves; + let svgWidth = svgTempWidth < width ? width - 5 : svgTempWidth; + const svgEL = select(`svg.${name}`); + const container = select(`.tree-view.${name}-container`); + svgEL.html(''); + svgEL.attr('height', svgHeight); + let canvasWidth = width; + if (svgTempWidth > width) { + if (allowScaleWidth) { + canvasWidth = svgTempWidth; + } + // we seems to have a margin of 25px that we can still see with text + if ( + svgTempWidth - 25 > width && + onRenderedBeyondWidth !== undefined + ) { + onRenderedBeyondWidth(); + } + } + svgEL.attr('width', canvasWidth); + let rootGroup = svgEL + .append('g') + .attr( + 'transform', + `translate(${svgWidth / 2 + nodeRadius},${nodeRadius * + 4}) rotate(90)` + ); + + // handle link + rootGroup + .selectAll('.link') + .data(root.descendants().slice(1)) + .enter() + .append('path') + .attr('class', 'link') + .attr('d', diagonal); + + let node = rootGroup + .selectAll('.node') + .data(root.descendants()) + .enter() + .append('g') + .attr( + 'class', + node => + `node ${node.children ? ' has-children' : ' leaf'} ${ + node.id === selectedNodeId ? 'selectedNode' : '' + } ${this.props.onNodeClick ? 'clickable' : ''}` + ) + .attr( + 'transform', + node => 'translate(' + node.y + ',' + node.x + ')' + ) + .on('click', node => this.onNodeClick(node)); + + node + .append('circle') + .attr('r', nodeRadius) + .attr('class', 'outer-circle'); + node + .append('circle') + .attr('r', nodeRadius - 3) + .attr('class', 'inner-circle'); + + node + .append('text') + .attr('y', nodeRadius / 4 + 1) + .attr('x', -nodeRadius * 1.8) + .text(node => node.data.name) + .attr('transform', 'rotate(-90)'); + + let selectedNode = selectedNodeId + ? root.descendants().find(node => node.id === selectedNodeId) + : null; + if (selectedNode) { + container.property( + 'scrollLeft', + svgWidth / 4 + + (svgWidth / 4 - 100) - + selectedNode.x / 30 * horizontalSpaceBetweenLeaves + ); + container.property( + 'scrollTop', + selectedNode.y / 100 * verticalSpaceBetweenNodes + ); + } else { + container.property( + 'scrollLeft', + svgWidth / 4 + (svgWidth / 4 - 100) + ); + } + } + } + + onNodeClick(node) { + if (this.props.onNodeClick) { + this.props.onNodeClick(node.data); + } + } +} + +export default Tree; |